Last active
May 26, 2017 17:38
-
-
Save aaronlevin/6963498b4c6c05f29f1d5e32c10321e0 to your computer and use it in GitHub Desktop.
Does the cats-mtl design support for-comprehensions with multiple Monad constraints? Looks like it does!
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package cats | |
import cats.data.{StateT, WriterT} | |
import cats.implicits._ | |
/** | |
* TypeLevel is working on some alternative encodings of the Monad Transformer | |
* Library (MTL). The existing solution in cats was broken for a few reasons. | |
* One annoying issue made it imposisible to use for-comprehension with more | |
* than one Monad$FOO constraint owing to implicit resolution ambiguities. | |
* | |
* I was curious if the new encoding that Edmund Nobel is working on caused | |
* (https://github.com/edmundnoble/cats-mtl) | |
* for-comphrenesion issues. It looks like it doesn't. | |
* | |
* (FYI: my implmenetation is based on this cats-mtl code and other issues | |
* discussed in the cats project but doens't necessarily reflect the current or | |
* final design.) | |
*/ | |
object MTL { | |
/** | |
* MonadState | |
*/ | |
trait MTLMonadState[F[_], S] { | |
val monad: Monad[F] | |
def get: F[S] | |
def set(s: S): F[Unit] | |
def modify(f: S => S): F[Unit] | |
} | |
/** | |
* MonadWriter | |
*/ | |
trait MTLMonadWriter[F[_], W] { | |
val monad: Monad[F] | |
def tell(w: W): F[Unit] | |
} | |
/** | |
* StateT is an MTLMonadState | |
*/ | |
implicit def stateTMTLMonadState[F[_], S]( | |
implicit | |
applicative: Applicative[F], | |
stateMonad: Monad[StateT[F,S,?]] | |
) = new MTLMonadState[StateT[F,S,?], S] { | |
val monad: Monad[StateT[F,S,?]] = stateMonad | |
def get = StateT.get | |
def set(s: S) = StateT.set(s) | |
def modify(f: S => S) = StateT.modify(f) | |
} | |
/** | |
* WriterT is a MTLMonadWriter | |
*/ | |
implicit def writerTMTLMonadWriter[F[_], W]( | |
implicit | |
monoid: Monoid[W], | |
applicative: Applicative[F], | |
writerMonad: Monad[WriterT[F,W,?]] | |
) = new MTLMonadWriter[WriterT[F,W,?],W] { | |
val monad = writerMonad | |
def tell(w: W) = WriterT.tell(w) | |
} | |
/** | |
* If (F,W) is a monad-writer, then (StateT[F,S,_], W) is a monad-writer | |
* | |
* MTLMonadWriter[F,W] => MonadWriter[StateT[F,S,?],W] | |
*/ | |
implicit def monadWriterImpliesMWState[F[_],W,S]( | |
implicit | |
monoid: Monoid[W], | |
mw: MTLMonadWriter[F,W], | |
applicative: Applicative[F], | |
stateMonad: Monad[StateT[F,S,?]] | |
) = new MTLMonadWriter[StateT[F,S,?],W] { | |
val monad = stateMonad | |
def tell(w: W) = StateT.lift(mw.tell(w)) | |
} | |
/** | |
* If (F,S) is a monad-state, then (WriterT[F,W,_], S) is a monad-state | |
* | |
* MTLMonadState[F,S] => MonadState[WriterT[F,W,?],S] | |
*/ | |
implicit def monadStateImpliesMSWriter[F[_],W,S]( | |
implicit | |
monoid: Monoid[W], | |
ms: MTLMonadState[F,S], | |
applicative: Applicative[F], | |
writerMonad: Monad[WriterT[F,W,?]] | |
) = new MTLMonadState[WriterT[F,W,?],S] { | |
val monad = writerMonad | |
def get = WriterT.lift(ms.get) | |
def set(s: S) = WriterT.lift(ms.set(s)) | |
def modify(f: S => S) = WriterT.lift(ms.modify(f)) | |
} | |
/** | |
* A small program that only requires a monad writer | |
*/ | |
def smallProgram[F[_]]( | |
implicit | |
monad: Monad[F], | |
mWriter: MTLMonadWriter[F,String] | |
): F[String] = for { | |
_ <- mWriter.tell("hello") | |
_ <- mWriter.tell("world") | |
} yield "!" | |
/** | |
* a function that makes some assumption about F without ever specifying it | |
* | |
* Specifically let's ensure that for-comprehension still works | |
*/ | |
def program[F[_]]( | |
implicit | |
monad: Monad[F], | |
mState: MTLMonadState[F,Int], | |
mWriter: MTLMonadWriter[F,String] | |
): F[Int] = for { | |
x <- monad.pure(10) | |
state <- mState.get | |
_ <- mState.set(11) | |
_ <- mWriter.tell("hi") | |
s <- smallProgram[F] // <-- can use smallprogram here, has less constraints | |
} yield x + state + s.length | |
/** | |
* let's make a concret F which is a monad stack with StateT, WriterT and Option. | |
*/ | |
type Stack1[A] = StateT[WriterT[Option,String,?],Int, A] | |
type Stack2[A] = WriterT[StateT[Option,Int,?],String, A] | |
/** | |
* force the implicit resolution and make sure it compiles | |
*/ | |
val stack1SmallProgram = smallProgram[Stack1] | |
val stack2SmallProgram = smallProgram[Stack2] | |
val stack1Program = program[Stack1] | |
val stack2Program = program[Stack2] | |
/** | |
* Let's evaluate the programs | |
*/ | |
val i1: Option[Int] = stack1Program.run(10).run.map{ case (log, (state, value)) => value } | |
// i1: Option[Int] = Some(21) | |
val i2: Option[Int] = stack2Program.run.run(10).map{ case (log, (state, value)) => value } | |
// i2: Option[Int] = Some(21) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
(I compiled this from within the
cats
project)