Skip to content

Instantly share code, notes, and snippets.

@aaronlevin
Last active May 26, 2017 17:38
Show Gist options
  • Select an option

  • Save aaronlevin/6963498b4c6c05f29f1d5e32c10321e0 to your computer and use it in GitHub Desktop.

Select an option

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
Copy Markdown
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