81
MONAD TRANSFORMERS In The ld

Monad Transformers In The Wild

Embed Size (px)

Citation preview

Page 1: Monad Transformers In The Wild

MONAD TRANSFORMERS

In The !ld

Page 3: Monad Transformers In The Wild

TWITTER: @_JRWEST

GITHUB.COM/JRWEST

BLOG.LOOPEDSTRANGE.COM

Page 4: Monad Transformers In The Wild

SF SCALA

May 2012

* http://marakana.com/s/scala_typeclassopedia_with_john_kodumal_of_atlassian_video,1198/index.html

Page 5: Monad Transformers In The Wild

trait Monad[F[_]] extends Applicative[F] {

def flatMap[A, B](fa: F[A])(f :A=>F[B]):F[B]

}

* monad type class * flatMap also called bind, >>=

Page 6: Monad Transformers In The Wild

def point[A](a: => A): M[A]

def map[A,B](ma: M[A])(f: A => B): M[B]

def flatMap[A,B](ma: M[A])(f: A => M[B]): M[B]

* the functions we care about* lift pure value, lift pure function, chain “operations”

Page 7: Monad Transformers In The Wild

scala> import scalaz.Monad

scala> import scalaz.std.option._

scala> val a = Monad[Option].point(1)

a: Option[Int] = Some(1)scala> Monad[Option].map(a)(_.toString + "hi")

res2: Option[java.lang.String] = Some(1hi)

scala> Monad[Option].bind(a)(i => if (i < 0) None else Some(i + 1))res4: Option[Int] = Some(2)

* explicit type class usage in scalaz seven

Page 8: Monad Transformers In The Wild

scala> import scalaz.syntax.monad._import scalaz.syntax.monad._scala> Option(1).flatMap(i => if (i < 0) None else Some(i+1))res6: Option[Int] = Some(2)scala> 1.point[Option].flatMap(...)res7: Option[Int] = Some(2)

* implicit type class usage in scalaz7 using syntax extensions

Page 9: Monad Transformers In The Wild

“A MONADIC FOR

COMPREHENSION IS AN

EMBEDDED PROGRAMMING

LANGUAGE WITH SEMANTICS

DEFINED BY THE MONAD”

* “one intuition of monads” - john

Page 10: Monad Transformers In The Wild

MULTIPLE EFFECTS

C"position

Page 11: Monad Transformers In The Wild

Option[A]

* it may not exist

Page 12: Monad Transformers In The Wild

SEMANTICSSIDE NOTE:

* to an extent, you can “choose” the meaning of a monad* Option -- anon. exceptions -- more narrowly, the exception that something is not there. Validation - monad/not monad - can mean different things in different contexts

Page 13: Monad Transformers In The Wild

IO[Option[A]]

* but side-effects are needed to even look for that value

Page 14: Monad Transformers In The Wild

IO[Validation[Throwable,Option[A]]

* and looking for that value may throw exceptions (or fail in some way)

Page 15: Monad Transformers In The Wild

IO[(List[String], Validation[Throwable,Option[A])]

* and logging what is going on is necessary

Page 16: Monad Transformers In The Wild

MULTIPLE EFFECTS

A P#oblem

Page 17: Monad Transformers In The Wild

MONADS DO NOT

COMPOSE

* the problem in theory (core issue)

Page 18: Monad Transformers In The Wild

“COMPOSE”?

Page 19: Monad Transformers In The Wild

FUNCTORSDO

COMPOSE

* as well as applicatives

Page 20: Monad Transformers In The Wild

trait Functor[F[_]] {

def map[A, B](fa: F[A])(f :A=>B):F[B]

}

Page 21: Monad Transformers In The Wild

def composeFunctor[M[_],N[_]](implicit m: Functor[M], n: Functor[N]) = new Functor[({type MN[A]=[M[N[A]]]})#MN] { def map[A,B](mna: M[N[A]])(f: A => B): M[N[B]] = ... }

* generic function that composes any two functors M[_] and N[_]

Page 22: Monad Transformers In The Wild

def composeFunctor[M[_],N[_]](implicit m: Functor[M], n: Functor[N]) = new Functor[({type MN[A]=[M[N[A]]]})#MN] { def map[A,B](mna: M[N[A]])(f: A => B): M[N[B]] = { M.map(mna)(na => N.map(na)(f)) } }

Page 23: Monad Transformers In The Wild

scala> Option("abc").map(f)res1: Option[Int] = Some(3)

scala> List(Option("abc"), Option("d"), Option("ef")).map2(f)res2: List[Option[Int]] = List(Some(3), Some(1), Some(2))

* can compose functors infinitely deep but...* scalaz provides method to compose 2, with nice syntatic sugar, easily (map2)

Page 24: Monad Transformers In The Wild

def notPossible[M[_],N[_]](implicit m: Monad[M], n: Monad[N]) = new Monad[({type MN[A]=[M[N[A]]]})#MN] { def flatMap[A,B](mna: M[N[A]])(f: A => M[N[B]]): M[N[B]] = ... }

* cannot write the same function for any two monads M[_], N[_]

Page 25: Monad Transformers In The Wild

def notPossible[M[_],N[_]](implicit m: Monad[M], n: Monad[N]) = new Monad[({type MN[A]=[M[N[A]]]})#MN] { def flatMap[A,B](mna: M[N[A]])(f: A => M[N[B]]): M[N[B]] = ... } TRY IT

!

* best way to understand this is attempt to write it yourself* it won’t compile

Page 26: Monad Transformers In The Wild

http://blog.tmorris.net/monads-do-not-compose/

* good resource to dive into this in more detail* some of previous slides based on above* provides template, in the form of a gist, for trying this stuff out

Page 27: Monad Transformers In The Wild

STAIR

STEPPING

* the problem in practice*http://www.flickr.com/photos/caliperstudio/2667302181/

Page 28: Monad Transformers In The Wild

val a: IO[Option[MyData]] = ...

val b: IO[Option[MyData]] = ...

* have two values that require we communicate w/ outside world to fetch* those values may not exist (alternative meaning, fetching may result in exceptions that are anonymous)

Page 29: Monad Transformers In The Wild

for { data1 <- a data2 <- b} yield { data1 merge data2 // fail}

* want to merge the two pieces of data if they both exist

Page 30: Monad Transformers In The Wild

for { // we've escaped IO, fail d1 <- a.unsafePerformIO d2 <- b.unsafePerformIO} yield d1 merge d2

* don’t want to perform the actions until later (don’t escape the IO monad)

Page 31: Monad Transformers In The Wild

for { od1 <- a od2 <- b} yield (od1,od2) match { case (Some(d1),Some(d2) => Option(d1 merge d2) case (a@Some(d1),_)) => a case (_,a@Some(d2)) => a case _ => None}

for { od1 <- a od2 <- b} yield for { d1 <- od1 d2 <- od2} yield d1 merge d2

* may notice the semi-group here* can also write it w/ an applicative* this is a contrived example

Page 32: Monad Transformers In The Wild

def b(data: MyData): IO[Option[MyData]BUT WHAT IF...

* even w/ simple example, this minor change throws a monkey wrench in things

Page 33: Monad Transformers In The Wild

for {

  readRes <- readIO(domain)

  res <- readRes.fold(

   success = _.cata(

    some = meta =>

if (meta.enabledStatus /== status) {

writeIO(meta.copy(enabledStatus = status))

} else meta.successNel[BarneyException].pure[IO],

     none = new ReadFailure(domain).failNel[AppMetadata].pure[IO]

    ),    failure = errors => errors.fail[AppMetadata].pure[IO]

  ) } yield res

):* example of what not to do from something I wrote a while back

Page 34: Monad Transformers In The Wild

MULTIPLE EFFECTS

A Solution

Page 35: Monad Transformers In The Wild

case class IOOption[A](run: IO[Option[A]])

define type that boxes box the value, doesn’t need to be a case class, similar to haskell newtype.

Page 36: Monad Transformers In The Wild

new Monad[IOOption] {

def point[A](a: => A): IOOption[A] = IOOption(a.point[Option].point[IO])

def map[A,B](fa: IOOption[A])(f: A => B): IOOption[B] = IOOption(fa.run.map(opt => opt.map(f)))

def flatMap[A, B](fa: IOOption[A])(f :A=>IOOption[B]):IOOption[B] =

IOOption(fa.run.flatMap((o: Option[A]) => o match { case Some(a) => f(a).run case None => (None : Option[B]).point[IO] }))}

* can define a Monad instance for new type

Page 37: Monad Transformers In The Wild

val a: IOOption[MyData] = ...val b: IOOption[MyData] = ...

val c: IOOption[MyData] = for { data1 <- a data2 <- b} yield { data1 merge data2}

val d: IO[Option[MyData]] = c.run

can use new type to improve previous contrived example

Page 38: Monad Transformers In The Wild

type MyState[A] = State[StateData,A]case class MyStateOption[A](run: MyState[Option[A]])

* what if we don’t need effects, but state we can read and write to produce a final optional value and some new state* State[S,A] where S is fixed is a monad* can define a new type for that as well

Page 39: Monad Transformers In The Wild

new Monad[MyStateOption] {

def map[A,B](fa: MyStateOption[A])(f: A => B): MyStateOption[B] =

MyStateOption(Functor[MyState].map(fa)(opt => opt.map(f)))

def flatMap[A, B](fa: MyStateOption[A])(f :A=>IOOption[B]) =

MyStateOption(Monad[MyState]].bind(fa)((o: Option[A]) => o match { case Some(a) => f(a).run case None => (None : Option[B]).point[MyState] }))}

new Monad[IOOption] {

def map[A,B](fa: IOOption[A])(f: A => B): IOOption[B] = IOOption(Functor[IO].map(fa)(opt => opt.map(f)))

def flatMap[A, B](fa: IOOption[A])(f :A=>IOOption[B]) =

IOOption(Monad[IO]].bind(fa)((o: Option[A]) => o match { case Some(a) => f(a).run case None => (None : Option[B]).point[IO] }))}

* opportunity for more abstraction* if you were going to do this, not exactly the way you would define these in real code, cheated a bit using {Functor,Monad}.apply

Page 40: Monad Transformers In The Wild

case class OptionT[M[_], A](run: M[Option[A]])

define a new type parameterized * -> * and *.

Page 41: Monad Transformers In The Wild

case class OptionT[M[_], A](run: M[Option[A]]) { def map[B](f: A => B)(implicit F: Functor[M]): OptionT[M,B] def flatMap[B](f: A => OptionT[M,B])(implicit M: Monad[M]): OptionT[M,B]}

* define map/flatMap a little differently, can be done like previous as typeclass instance but convention is to define the interface on the transformer and later define typeclass instance using the interface

Page 42: Monad Transformers In The Wild

case class OptionT[M[_], A](run: M[Option[A]]) { def map[B](f: A => B)(implicit F: Functor[M]): OptionT[M,B] = OptionT[M,B](F.map(run)((o: Option[A]) => o map f)) def flatMap[B](f: A => OptionT[M,B])(implicit M: Monad[M]): OptionT[M,B] = OptionT[M,B](M.bind(run)((o: Option[A]) => o match { case Some(a) => f(a).run case None => M.point((None: Option[B])) }))}

* implementations resemble what has already been shown

Page 43: Monad Transformers In The Wild

new Monad[IOOption] {

def map[A,B](fa: IOOption[A])(f: A => B): IOOption[B] = IOOption(Functor[IO].map(fa)(opt => opt.map(f)))

def flatMap[A, B](fa: IOOption[A])(f :A=>IOOption[B]) =

IOOption(Monad[IO]].bind(fa)((o: Option[A]) => o match { case Some(a) => f(a).run case None => (None : Option[B]).point[IO] }))}

case class OptionT[M[_], A](run: M[Option[A]]) { def map[B](f: A => B)(implicit F: Functor[M]): OptionT[M,B] = OptionT[M,B](F.map(run)((o: Option[A]) => o map f)) def flatMap[B](f: A => OptionT[M,B])(implicit M: Monad[M]) = OptionT[M,B](M.bind(run)((o: Option[A]) => o match { case Some(a) => f(a).run case None => M.point((None: Option[B])) }))}

* it the generalization of what was written before

Page 44: Monad Transformers In The Wild

type FlowState[A] = State[ReqRespData, A]val f: Option[String] => FlowState[Boolean] = (etag: Option[String]) => { val a: OptionT[FlowState, Boolean] = for { // string <- OptionT[FlowState,String]     e <- optionT[FlowState](etag.point[FlowState]) // wrap FlowState[Option[String]] in OptionT     matches <- optionT[FlowState]((requestHeadersL member IfMatch))   } yield matches.split(",").map(_.trim).toList.contains(e) a getOrElse false // FlowState[Boolean]}

* check existence of etag in an http request, data lives in state* has minor bug, doesn’t deal w/ double quotes as written* https://github.com/stackmob/scalamachine/blob/master/core/src/main/scala/scalamachine/core/v3/WebmachineDecisions.scala#L282-285

Page 45: Monad Transformers In The Wild

val reqCType: OptionT[FlowState,ContentType] = for {      contentType <- optionT[FlowState]( (requestHeadersL member ContentTypeHeader) )      mediaInfo <- optionT[FlowState]( parseMediaTypes(contentType).headOption.point[FlowState] )} yield mediaInfo.mediaRange

* determine content type of the request, data lives in state, may not be specified* https://github.com/stackmob/scalamachine/blob/master/core/src/main/scala/scalamachine/core/v3/WebmachineDecisions.scala#L772-775

Page 46: Monad Transformers In The Wild

scala> type EitherTString[M[_],A] = EitherT[M,String,A]defined type alias EitherTString

scala> val items = eitherT[List,String,Int](List(1,2,3,4,5,6).map(Right(_)))items: scalaz.EitherT[List,String,Int] = ...

* adding features to a “embedded language”

Page 47: Monad Transformers In The Wild

for { i <- items } yield print(i)// 123456

for { i <- items _ <- if (i > 4) leftT[List,String,Unit]("fail") else rightT[List,String,Unit](()) } yield print(i)// 1234

* adding error handling, and early termination to non-deterministic computation

Page 48: Monad Transformers In The Wild

MONAD

TRANSFORMERS

In General

Page 49: Monad Transformers In The Wild

MyMonad[A]

Page 50: Monad Transformers In The Wild

NAMING CONVENTION

MyMonadT[M[_], A]

* transformer name ends in T

Page 51: Monad Transformers In The Wild

BOXES A VALUE

run: M[MyMonad[A]

* value is typically called “run” in scalaz7* often called “value” in scalaz6 (because of NewType)

Page 52: Monad Transformers In The Wild

A MONAD TRANSFORMER

IS A MONAD TOO

* i mean, its thats kinda the point of this whole exercise isn’t it :)

Page 53: Monad Transformers In The Wild

def optTMonad[M[_] : Monad] = new Monad[({type O[X]=OptionT[M,X]]})#O) { def point[A](a: => A): OptionT[M,A] = OptionT(a.point[Option].point[M]) def map[A,B](fa: OptionT[M,A])(f: A => B): OptionT[M,B] = fa map f def flatMap[A, B](fa: OptionT[M,A])(f :A=> OptionT[M,B]): OptionT[M, B] = fa flatMap f}

* monad instance definition for OptionT

Page 54: Monad Transformers In The Wild

HAS INTERFACE RESEMBLING UNDERLYING

MONAD’S INTERFACE

* can interact with the monad transformer in a manner similar to working with the actual monad* same methods, slightly different type signatures* different from haskell, “feature” of scala, since we can define methods on a type

Page 55: Monad Transformers In The Wild

case class OptionT[M[_], A](run: M[Option[A]]) { def getOrElse[AA >: A](d: => AA)(implicit F: Functor[M]): M[AA] = F.map(run)((_: Option[A]) getOrElse default) def orElse[AA >: A](o: OptionT[M,AA])(implicit M: Monad[M]): OptionT[M,AA] = OptionT[M,AA](M.bind(run) { case x@Some(_) => M.point(x) case None => o.run }}

Page 56: Monad Transformers In The Wild

MONAD

TRANSFORMERS

Stacked Effects

Page 57: Monad Transformers In The Wild

TRANSFORMER IS A MONAD

⇒TRANSFORMER CAN WRAP ANOTHER TRANSFORMER

* at the start, the goal was to stack effects (not just stack 2 effects)* this makes it possible

Page 58: Monad Transformers In The Wild

type VIO[A] = ValidationT[IO,Throwable,A]

def doWork(): VIO[Option[Int]] = ...

val r: OptionT[VIO,Int] = optionT[VIO](doWork())

* wrap the ValidationT with success type Option[A] in an OptionT* define type alias for connivence -- avoids nasty type lambda syntax inline

Page 59: Monad Transformers In The Wild

val action: OptionT[VIO, Boolean] = for { devDomain <- optionT[VIO] {    validationT(       bucket.fetch[CName]("%s.%s".format(devPrefix,hostname))       ).mapFailure(CNameServiceException(_))   } _ <- optionT[VIO] { validationT(deleteDomains(devDomain)).map(_.point[Option]) }} yield true

* code (slightly modified) from one of stackmob’s internal services* uses Scaliak to fetch hostname data from riak and then remove them* possible to clean this code up a bit, will discuss shortly (monadtrans)

Page 60: Monad Transformers In The Wild

KEEP ON STACKIN’

ON

* don’t have to stop at 2 levels deep, our new stack is monad too* each monad/transformer we add to the stack compose more types of effects

Page 61: Monad Transformers In The Wild

“ORDER” MATTERS

* how stack is built, which transformers wrap which monads, determines the overall semantics of the entire stack* changing that order can, and usually does, change semantics

Page 62: Monad Transformers In The Wild

OptionT[FlowState, A]vs.

StateT[Option,ReqRespData,A]

* what is the difference in semantics between the two? * type FlowState[A] = State[ReqRespData,A]

Page 63: Monad Transformers In The Wild

FlowState[Option[A]]vs.

Option[State[ReqRespData,A]

* unboxing makes things easier to see* a state action that returns an optional value vs a state action that may not exist* the latter probably doesn’t make as much sense in the majority of cases

Page 64: Monad Transformers In The Wild

MONADTRANS

The Type Class

* type classes beget more type classes

Page 65: Monad Transformers In The Wild

REMOVING REPETITION===

MORE ABSTRACTION

* previous examples have had a repetitive, annoying, & verbose task* can be abstracted away...by a type class of course

Page 66: Monad Transformers In The Wild

optionT[VIO](validationT(deleteDomains(devDomain)).map(_.point[Option]))eitherT[List,String,Int](List(1,2,3,4,5,6).map(Right(_))) resT[FlowState](encodeBodyIfSet(resource).map(_.point[Res]))

* some cases require lifting the value into the monad and then wrap it in the transformer* from previous examples

Page 67: Monad Transformers In The Wild

M[A] -> M[N[A]] -> NT[M[N[_]], A]

* this is basically what we are doing every time* taking some monad M[A], lifting A into N, a monad we have a transformer for, and then wrapping all of that in N’s monad transformer

Page 68: Monad Transformers In The Wild

trait MonadTrans[F[_[_], _]] {

  def liftM[G[_] : Monad, A](a: G[A]): F[G, A]

}

* liftM will do this for any transformer F[_[_],_] and any monad G[_] provided an instance of it is defined for F[_[_],_]

Page 69: Monad Transformers In The Wild

 def liftM[G[_], A](a: G[A])(implicit G: Monad[G]): OptionT[G, A] =

    OptionT[G, A](G.map[A, Option[A]](a)((a: A) => a.point[Option]))

* full definition requires some type ceremony* https://github.com/scalaz/scalaz/blob/scalaz-seven/core/src/main/scala/scalaz/OptionT.scala#L155-156

Page 70: Monad Transformers In The Wild

def liftM[G[_], A](ga: G[A])(implicit G: Monad[G]): ResT[G,A] =

      ResT[G,A](G.map(ga)(_.point[Res]))

* implementation for scalamachine’s Res monad* https://github.com/stackmob/scalamachine/blob/master/scalaz7/src/main/scala/scalamachine/scalaz/res/ResT.scala#L75-76

Page 71: Monad Transformers In The Wild

encodeBodyIfSet(resource).liftM[OptionT]List(1,2,3).liftM[EitherTString]validationT(deleteDomains(devDomain)).liftM[OptionT]

* cleanup of previous examples* method-like syntax requires a bit more work: https://github.com/scalaz/scalaz/blob/scalaz-seven/core/src/main/scala/scalaz/syntax/MonadSyntax.scala#L9

Page 72: Monad Transformers In The Wild

for { media <- (metadataL >=> contentTypeL).map(_ | ContentType("text/plain")).liftM[ResT]   charset <- (metadataL >=> chosenCharsetL).map2(";charset=" + _).getOrElse("")).liftM[ResT]   _ <- (responseHeadersL += (ContentTypeHeader, media.toHeader + charset)).liftM[ResT]   mbHeader <- (requestHeadersL member AcceptEncoding).liftM[ResT]   decision <- mbHeader >| f7.point[ResTFlow] | chooseEncoding(resource, "identity;q=1.0,*;q=0.5")} yield decision

* https://github.com/stackmob/scalamachine/blob/master/core/src/main/scala/scalamachine/core/v3/WebmachineDecisions.scala#L199-205

Page 73: Monad Transformers In The Wild

MONAD

TRANSFORMERS

In Review

Page 74: Monad Transformers In The Wild

STACKINGMONADS

COMPOSES EFFECTS

* when monads are stacked an embedded language is being built with multiple effects* this is not the only intuition of monads/transformers

Page 75: Monad Transformers In The Wild

CAN NOT COMPOSE MONADS

GENERICALLY

* cannot write generic function to compose any two monads M[_], N[_] like we can for any two functors

Page 76: Monad Transformers In The Wild

MONAD TRANSFORMERSCOMPOSE M[_] : MONAD WITH

ANY N[_] : MONAD

* can’t compose any two, but can compose a given one with any other

Page 77: Monad Transformers In The Wild

MONAD TRANSFORMERS WRAP OTHER

MONAD TRANSFORMERS

* monad transformers are monads* so they can be the N[_] : Monad that the transformer composes with its underlying monad

Page 78: Monad Transformers In The Wild

MONADTRANSREDUCES

REPETITION

* often need to take a value that is not entirely lifted into a monad transformer stack and do just that

Page 79: Monad Transformers In The Wild

STACK MONADSDON’T

STAIR-STEP

* monad transformers reduce ugly, stair-stepping or nested code and focuses on core task* focuses on intuition of mutiple effects instead of handling things haphazardly

Page 80: Monad Transformers In The Wild

THANK YOU

* stackmob, markana, john & atlassian, other sponsors, cosmin

Page 81: Monad Transformers In The Wild

QUESTIONS?