-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
FreeT stack-safe transformer #1266
Changes from 16 commits
dd3419a
43d08cb
80ac0d5
5a616e8
ee890fb
a4bfec8
a9a86d3
9654b3d
014ff40
11a11ae
3245900
336a71d
5575b14
fd119d8
a7e690f
1ebc15c
f4d8693
2c515dc
ce62750
16733ea
ff46b5b
a15b267
ba3c1d6
c7d8089
d75686d
c416127
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,263 @@ | ||
package cats | ||
package free | ||
|
||
import scala.annotation.tailrec | ||
|
||
import cats.data.Xor | ||
|
||
/** | ||
* FreeT is a monad transformer for Free monads over a Functor S | ||
* | ||
* Stack safety for `Free` and `FreeT` is based on the paper | ||
* [[http://functorial.com/stack-safety-for-free/index.pdf Stack Safety for Free]] by Phil Freeman | ||
* | ||
* This Scala implementation of `FreeT` and its usages are derived from | ||
* [[https://github.com/scalaz/scalaz/blob/series/7.3.x/core/src/main/scala/scalaz/FreeT.scala Scalaz's FreeT]], | ||
* originally written by Brian McKenna. | ||
*/ | ||
sealed abstract class FreeT[S[_], M[_], A] extends Product with Serializable { | ||
|
||
import FreeT._ | ||
|
||
final def map[B](f: A => B)(implicit M: Applicative[M]): FreeT[S, M, B] = | ||
flatMap(a => pure(f(a))) | ||
|
||
/** Binds the given continuation to the result of this computation. */ | ||
final def flatMap[B](f: A => FreeT[S, M, B]): FreeT[S, M, B] = | ||
FlatMapped(this, f) | ||
|
||
/** | ||
* Changes the underlying `Monad` for this `FreeT`, ie. | ||
* turning this `FreeT[S, M, A]` into a `FreeT[S, N, A]`. | ||
*/ | ||
def hoist[N[_]](mn: M ~> N): FreeT[S, N, A] = | ||
step match { | ||
case e @ FlatMapped(_, _) => | ||
FlatMapped(e.a.hoist(mn), e.f.andThen(_.hoist(mn))) | ||
case Suspend(m) => | ||
Suspend(mn(m)) | ||
} | ||
|
||
/** Change the base functor `S` for a `FreeT` action. */ | ||
def interpret[T[_]](st: S ~> T)(implicit M: Functor[M]): FreeT[T, M, A] = | ||
step match { | ||
case e @ FlatMapped(_, _) => | ||
FlatMapped(e.a.interpret(st), e.f.andThen(_.interpret(st))) | ||
case Suspend(m) => | ||
Suspend(M.map(m)(_.map(s => st(s)))) | ||
} | ||
|
||
/** | ||
* Runs to completion, mapping the suspension with the given transformation | ||
* at each step and accumulating into the monad `M`. | ||
*/ | ||
def foldMap(f: S ~> M)(implicit MR: Monad[M], RT: RecursiveTailRecM[M]): M[A] = { | ||
def go(ft: FreeT[S, M, A]): M[FreeT[S, M, A] Xor A] = | ||
ft match { | ||
case Suspend(ma) => MR.flatMap(ma) { | ||
case Xor.Left(a) => MR.pure(Xor.Right(a)) | ||
case Xor.Right(sa) => MR.map(f(sa))(Xor.right) | ||
} | ||
case g @ FlatMapped(_, _) => g.a match { | ||
case Suspend(mx) => MR.flatMap(mx) { | ||
case Xor.Left(x) => MR.pure(Xor.left(g.f(x))) | ||
case Xor.Right(sx) => MR.map(f(sx))(g.f andThen Xor.left) | ||
} | ||
case g0 @ FlatMapped(_, _) => MR.pure(Xor.left(g0.a.flatMap(g0.f(_).flatMap(g.f)))) | ||
} | ||
} | ||
|
||
MR.tailRecM(this)(go) | ||
} | ||
|
||
/** Evaluates a single layer of the free monad */ | ||
def resume(implicit S: Functor[S], MR: Monad[M], RT: RecursiveTailRecM[M]): M[A Xor S[FreeT[S, M, A]]] = { | ||
def go(ft: FreeT[S, M, A]): M[FreeT[S, M, A] Xor (A Xor S[FreeT[S, M, A]])] = | ||
ft match { | ||
case Suspend(f) => MR.map(f)(as => Xor.right(as.map(S.map(_)(pure(_))))) | ||
case g1 @ FlatMapped(_, _) => g1.a match { | ||
case Suspend(m1) => MR.map(m1) { | ||
case Xor.Left(a) => Xor.left(g1.f(a)) | ||
case Xor.Right(fc) => Xor.right(Xor.right(S.map(fc)(g1.f(_)))) | ||
} | ||
case g2 @ FlatMapped(_, _) => MR.pure(Xor.left(g2.a.flatMap(g2.f(_).flatMap(g1.f)))) | ||
} | ||
} | ||
|
||
MR.tailRecM(this)(go) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same comment as below |
||
} | ||
|
||
/** | ||
* Runs to completion, using a function that maps the resumption from `S` to a monad `M`. | ||
*/ | ||
def runM(interp: S[FreeT[S, M, A]] => M[FreeT[S, M, A]])(implicit S: Functor[S], MR: Monad[M], RT: RecursiveTailRecM[M]): M[A] = { | ||
def runM2(ft: FreeT[S, M, A]): M[FreeT[S, M, A] Xor A] = | ||
MR.flatMap(ft.resume) { | ||
case Xor.Left(a) => MR.pure(Xor.right(a)) | ||
case Xor.Right(fc) => MR.map(interp(fc))(Xor.left) | ||
} | ||
MR.tailRecM(this)(runM2) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. you should do: |
||
} | ||
|
||
/** | ||
* Finds the first `M` instance, `m`, and maps it to contain the rest | ||
* of the computation. Since only `map` is used on `m`, its structure | ||
* is preserved. | ||
*/ | ||
@tailrec | ||
private[cats] final def toM(implicit M: Applicative[M]): M[FreeT[S, M, A]] = | ||
this match { | ||
case Suspend(m) => M.map(m) { | ||
case Xor.Left(a) => pure(a) | ||
case Xor.Right(s) => liftF(s) | ||
} | ||
case g1 @ FlatMapped(_, _) => g1.a match { | ||
case Suspend(m) => M.map(m) { | ||
case Xor.Left(a) => g1.f(a) | ||
case Xor.Right(s) => liftF[S, M, g1.A](s).flatMap(g1.f) | ||
} | ||
case g0 @ FlatMapped(_, _) => g0.a.flatMap(g0.f(_).flatMap(g1.f)).toM | ||
} | ||
} | ||
|
||
@tailrec | ||
private def step: FreeT[S, M, A] = | ||
this match { | ||
case g @ FlatMapped(_, _) => g.a match { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. what's the rationale for same below. Also, why not: this match {
case FlatMapped(FlatMapped(a, fn0), fn1) =>
a.flatMap(a => fn0(a).flatMap(fn1)).step
case nonNestedFlatMap => nonNestedFlatMap
} There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These are for the same reasons as #1266 (comment) Inference does not work when referring to an abstract type member. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. scala!!!!!!!!!! Okay. |
||
case g0 @ FlatMapped(_, _) => g0.a.flatMap(a => g0.f(a).flatMap(g.f)).step | ||
case _ => g | ||
} | ||
case x => x | ||
} | ||
|
||
override def toString(): String = "FreeT(...)" | ||
} | ||
|
||
object FreeT extends FreeTInstances { | ||
/** Suspend the computation with the given suspension. */ | ||
private[free] case class Suspend[S[_], M[_], A](a: M[A Xor S[A]]) extends FreeT[S, M, A] | ||
|
||
/** Call a subroutine and continue with the given function. */ | ||
private[free] case class FlatMapped[S[_], M[_], A0, B](a0: FreeT[S, M, A0], f0: A0 => FreeT[S, M, B]) extends FreeT[S, M, B] { | ||
type A = A0 | ||
def a: FreeT[S, M, A] = a0 | ||
def f: A => FreeT[S, M, B] = f0 | ||
} | ||
|
||
/** Return the given value in the free monad. */ | ||
def pure[S[_], M[_], A](value: A)(implicit M: Applicative[M]): FreeT[S, M, A] = Suspend(M.pure(Xor.left(value))) | ||
|
||
def suspend[S[_], M[_], A](a: M[A Xor S[FreeT[S, M, A]]])(implicit M: Applicative[M]): FreeT[S, M, A] = | ||
liftT(a).flatMap({ | ||
case Xor.Left(a) => pure(a) | ||
case Xor.Right(s) => roll(s) | ||
}) | ||
|
||
def tailRecM[S[_], M[_]: Applicative, A, B](a: A)(f: A => FreeT[S, M, A Xor B]): FreeT[S, M, B] = | ||
f(a).flatMap { | ||
case Xor.Left(a0) => tailRecM(a0)(f) | ||
case Xor.Right(b) => pure[S, M, B](b) | ||
} | ||
|
||
def liftT[S[_], M[_], A](value: M[A])(implicit M: Functor[M]): FreeT[S, M, A] = | ||
Suspend(M.map(value)(Xor.left)) | ||
|
||
/** A version of `liftT` that infers the nested type constructor. */ | ||
def liftTU[S[_], MA](value: MA)(implicit M: Unapply[Functor, MA]): FreeT[S, M.M, M.A] = | ||
liftT[S, M.M, M.A](M.subst(value))(M.TC) | ||
|
||
/** Suspends a value within a functor in a single step. Monadic unit for a higher-order monad. */ | ||
def liftF[S[_], M[_], A](value: S[A])(implicit M: Applicative[M]): FreeT[S, M, A] = | ||
Suspend(M.pure(Xor.right(value))) | ||
|
||
def roll[S[_], M[_], A](value: S[FreeT[S, M, A]])(implicit M: Applicative[M]): FreeT[S, M, A] = | ||
liftF[S, M, FreeT[S, M, A]](value).flatMap(identity) | ||
|
||
} | ||
|
||
private[free] sealed trait FreeTInstances3 { | ||
implicit def catsFreeMonadStateForFreeT[S[_], M[_], E](implicit M1: MonadState[M, E]): MonadState[FreeT[S, M, ?], E] = | ||
new MonadState[FreeT[S, M, ?], E] with FreeTMonad[S, M] { | ||
override def M = implicitly | ||
override def get = | ||
FreeT.liftT(M1.get) | ||
override def set(s: E) = | ||
FreeT.liftT(M1.set(s)) | ||
} | ||
} | ||
|
||
private[free] sealed trait FreeTInstances2 extends FreeTInstances3 { | ||
implicit def catsFreeMonadErrorForFreeT[S[_], M[_]: RecursiveTailRecM, E](implicit E: MonadError[M, E]): MonadError[FreeT[S, M, ?], E] = | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why do we need There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Constrains the nested M to be also stack safe but if you think is not necessary we can remove this constrain. |
||
new MonadError[FreeT[S, M, ?], E] with FreeTMonad[S, M] { | ||
override def M = implicitly | ||
override def handleErrorWith[A](fa: FreeT[S, M, A])(f: E => FreeT[S, M, A]) = | ||
FreeT.liftT[S, M, FreeT[S, M, A]](E.handleErrorWith(fa.toM)(f.andThen(_.toM)))(M).flatMap(identity) | ||
override def raiseError[A](e: E) = | ||
FreeT.liftT(E.raiseError[A](e))(M) | ||
} | ||
} | ||
|
||
private[free] sealed trait FreeTInstances1 extends FreeTInstances2 { | ||
implicit def catsFreeFlatMapForFreeT[S[_], M[_]](implicit M0: Applicative[M]): FlatMap[FreeT[S, M, ?]] = | ||
new FreeTFlatMap[S, M] { | ||
implicit def M: Applicative[M] = M0 | ||
} | ||
|
||
implicit def catsFreeTransLiftForFreeT[S[_]]: TransLift.Aux[FreeT[S, ?[_], ?], Functor] = | ||
new TransLift[FreeT[S, ?[_], ?]] { | ||
|
||
type TC[M[_]] = Functor[M] | ||
|
||
override def liftT[M[_]: Functor, A](ma: M[A]): FreeT[S, M, A] = | ||
FreeT.liftT(ma) | ||
} | ||
} | ||
|
||
private[free] sealed trait FreeTInstances0 extends FreeTInstances1 { | ||
implicit def catsFreeMonadForFreeT[S[_], M[_]](implicit M0: Applicative[M]): Monad[FreeT[S, M, ?]] with RecursiveTailRecM[FreeT[S, M, ?]] = | ||
new FreeTMonad[S, M] { | ||
def M = M0 | ||
} | ||
|
||
implicit def catsFreeCombineForFreeT[S[_], M[_]: Applicative: RecursiveTailRecM: SemigroupK]: SemigroupK[FreeT[S, M, ?]] = | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think you need |
||
new FreeTCombine[S, M] { | ||
override def M = implicitly | ||
override def M1 = implicitly | ||
override def M2 = implicitly | ||
} | ||
} | ||
|
||
private[free] sealed trait FreeTInstances extends FreeTInstances0 { | ||
implicit def catsFreeMonadCombineForFreeT[S[_], M[_]: Alternative: RecursiveTailRecM]: MonadCombine[FreeT[S, M, ?]] = | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think we should take |
||
new MonadCombine[FreeT[S, M, ?]] with FreeTCombine[S, M] with FreeTMonad[S, M] { | ||
override def M = implicitly | ||
override def M1 = implicitly | ||
override def M2 = implicitly | ||
|
||
override def empty[A] = FreeT.liftT[S, M, A](MonoidK[M].empty[A])(M) | ||
} | ||
} | ||
|
||
private[free] sealed trait FreeTFlatMap[S[_], M[_]] extends FlatMap[FreeT[S, M, ?]] { | ||
implicit def M: Applicative[M] | ||
|
||
override final def map[A, B](fa: FreeT[S, M, A])(f: A => B): FreeT[S, M, B] = fa.map(f) | ||
def flatMap[A, B](fa: FreeT[S, M, A])(f: A => FreeT[S, M, B]): FreeT[S, M, B] = fa.flatMap(f) | ||
override final def tailRecM[A, B](a: A)(f: A => FreeT[S, M, A Xor B]): FreeT[S, M, B] = | ||
FreeT.tailRecM(a)(f) | ||
} | ||
|
||
private[free] sealed trait FreeTMonad[S[_], M[_]] extends Monad[FreeT[S, M, ?]] with RecursiveTailRecM[FreeT[S, M, ?]] with FreeTFlatMap[S, M] { | ||
implicit def M: Applicative[M] | ||
|
||
override final def pure[A](a: A): FreeT[S, M, A] = | ||
FreeT.pure[S, M, A](a) | ||
} | ||
|
||
private[free] sealed trait FreeTCombine[S[_], M[_]] extends SemigroupK[FreeT[S, M, ?]] { | ||
implicit def M: Applicative[M] | ||
implicit def M1: RecursiveTailRecM[M] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why is this here? |
||
def M2: SemigroupK[M] | ||
override final def combineK[A](a: FreeT[S, M, A], b: FreeT[S, M, A]): FreeT[S, M, A] = | ||
FreeT.liftT(M2.combineK(a.toM, b.toM))(M).flatMap(identity) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
RT.sameType(MR).tailRecM(this)(go)