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
{ 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
* 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)
(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
_.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
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
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
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
= 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
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
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
= 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
= (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
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
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”
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
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
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
{ 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)
* 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
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
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
implementation for scalamachine’s Res monad * https://github.com/stackmob/scalamachine/blob/master/scalaz7/src/main/scala/scalamachine/scalaz/res/ ResT.scala#L75-76