Skip to content

Instantly share code, notes, and snippets.

@aaronlevin
Last active May 26, 2017 17:38
Show Gist options
  • Save aaronlevin/6963498b4c6c05f29f1d5e32c10321e0 to your computer and use it in GitHub Desktop.
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!
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)
}
@aaronlevin
Copy link
Author

(I compiled this from within the cats project)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment