Scalaz(25)- Monad: Monad Transformer-叠加Monad效果

  中间插播了几篇scalaz数据类型,今后又要回去Monad专项论题。因为FP的特征就是Monad式编制程序(Monadic
programming),所以必需充裕知情认知Monad、熟谙精晓Monad运用。曾经看到一段对Monad的汇报:“Monadic
for-comprehension就是一种嵌入式编制程序语言,由它的Monad提供它的语法”。但假诺种种Monad的for-comprehension都单身提供一套语法的话,这种编制程序语言就展现煞是清淡、效能轻松了。那么既然是FP,大家应该可以通过函数组合(function
composition)来把数不尽大致的for-comprehension组合成一套完善的编制程序语言吧?比方那样:Option[A]
>>> IO[Option[A]] >>>
IO[Either[String,Option[A]]。恰恰,Monad是不帮忙函数组合的。先掌握一下函数组合:Functor是能够组合的,大家得以把fa和fb组合成三个更目迷五色的Functor
fab,大家来证雅培(Abbott)下:

 def composeFunctor[M[_],N[_]](fa: Functor[M], fb: Functor[N]): Functor[({type mn[x] = M[N[x]]})#mn] =
   new Functor[({type mn[x] = M[N[x]]})#mn] {
        def map[A,B](fab: M[N[A]])(f: A => B): M[N[B]] =
           fa.map(fab)(n => fb.map(n)(f))
   }                                              //> composeFunctor: [M[_], N[_]](fa: scalaz.Functor[M], fb: scalaz.Functor[N])s
                                                  //| calaz.Functor[[x]M[N[x]]]

咱俩来解释一下:假若M,N都以Functor,那么M[N[A]]也是Functor,我们得以用M[N[A]].map来运算A值。看看上面包车型地铁例证:

1 val stringlen: String => Int = _.length           //> stringlen  : String => Int = <function1>
2 val optionInList = List("1".some,"12".some,"123".some)
3                                                   //> optionInList  : List[Option[String]] = List(Some(1), Some(12), Some(123))
4 
5 val mnFunctor = composeFunctor(Functor[List],Functor[Option])
6                                                   //> mnFunctor  : scalaz.Functor[[x]List[Option[x]]] = Exercises.monadtrans$$ano
7                                                   //| nfun$main$1$$anon$1@130d63be
8 mnFunctor.map(optionInList)(stringlen)            //> res3: List[Option[Int]] = List(Some(1), Some(2), Some(3))

那么大家须求的Monad组合应该是这么的:M[N[A]],M,N都是Monad,如:Either[String,Option[A]],甚至是M[N[P[A]]],三层Monad。可惜,不是富有Monad都补助函数组合的,看上面:

 def composeMonad[M[_],N[_]](ma: Monad[M], mb: Monad[N]): Monad[({type mn[x] = M[N[x]]})#mn] =
   new Monad[({type mn[x] = M[N[x]]})#mn] {
     def point[A](a: => A) = ma.point(mb.point(a))
        def bind[A,B](mab: M[N[A]])(f: A => M[N[B]]): M[N[B]] =
           ??? ...
   }

实现M[N[A]].bind是不容许的,我们能够尝试。这就堵死了函数组合那条路。难道大家就不能使用M[N[A]]这样的for-comprehension了吗?毕竟像Either[String,Option[A]]那般的整合是很常见的呦,比方说从数据Curry读取那样的动作,有两种可能:获得数据、无多少None、发生错误。无论怎么着大家先试试用for-comprehension:

1 type Result[A] = String \/ Option[A]
2 val result: Result[Int] = 62.some.right           //> result  : Exercises.monadtxns.Result[Int] = \/-(Some(62))
3 for {
4     optionValue <- result
5 } yield {
6   for {
7       valueA <- optionValue
8   } yield valueA + 18                             //> res0: scalaz.\/[String,Option[Int]] = \/-(Some(80))
9 }

从上面能够精通大家必需用两层for-comprehension能力运算A值。那么综上说述假诺是M[N[P[A]]]就需求三层for-comprehension了。那正是所谓的“下阶梯式算法”(stair-stepping)。表面上来看stair-stepping会生出复杂臃肿的代码,丧失FP的简要优雅风格。但想深一层,要是中间一个Monad是会爆发副效用的如IO[Option[A]],那么地点的事例就改为那样:

1 for {
2   optionData <- IO
3 } yield {
4   for {
5     data <- optionData
6   } yield Process(data)
7 }

我们看来在率先层运算里张开了IO运算,发生了负效应。那么以上的代码就不再是纯代码了,无法保证函数组合。也正是说stair-stepping会发出不纯代码,违背了FP须要。从前大家曾经斟酌过 ReaderWriterState Monad,它是Reader,Writer,State五个Monad的结合。在它的for-comprehension里的演算结果类型是ReaderWriterState一种,所以并未stair-stepping焦心。但大家必得先创建多个新的门类(不是由此函数组合的新类型)。难道大家在运用分裂须求的for-comprehension时都急需再行创建三个新类型吗,那样不就损失了FP的代码重复使用特点了呢?不,scalaz提供的Monad
Transformer正是三个实用的施工方案。

scalaz为无数type
class提供了Monad
Transformer,它们都是T尾缀命名如OptionT、EitherT、StateT…,大家能够通过Monad
Transformer来灵活地整合Monad。以OptionT为例:

1 type Error[A] = \/[String, A]
2 type Result[A] = OptionT[Error, A]
3 
4 val result: Result[Int] = 62.point[Result]        //> result  : Exercises.monadtxns.Result[Int] = OptionT(\/-(Some(62)))
5 val transformed =
6   for {
7     value <- result
8   } yield value + 18                              //> transformed  : scalaz.OptionT[Exercises.monadtxns.Error,Int] = OptionT(\/-(S
9                                                   //| ome(80)))

前段时间,运算A只须求一层context了。Result正是由此Monad
Transformer爆发的新类型。在上边包车型客车类型营造里,OptionT就是八个Monad
Transformer、Error是原则性了Left类型的Either。因为Either有三个品种参数,大家实际也足以直接用type
lambda来代表Result[A]:

type Result[A] = OptionT[({type l[x] = \/[String,x]})#l,A]

唯独那样写不但复杂,况兼会影响编写翻译器的品类推导(compiler
type inference)。

值得注意的是,Monad
Transformer 类型的营造是由内向外反向的。比方上面包车型大巴例子中OptionT是个Monad
Transformer,它的档案的次序款式是OptionT[M[_],A]。OptionT实际上是用来营造M[Option[A]],在大家的例子里正是Either[Option[A]]。大家来探视一些常用Monad
Transformer的花色款式:

final case class OptionT[F[_], A](run: F[Option[A]]) {
...
final case class EitherT[F[_], A, B](run: F[A \/ B]) {
...
final case class ListT[F[_], A](run: F[List[A]]){
...
trait IndexedStateT[F[_], -S1, S2, A] { self =>
  /** Run and return the final value and state in the context of `F` */
  def apply(initial: S1): F[(S2, A)]

能够看到,Monad
Transformer
的主要性职能就在重组run这一个我们誉为嵌入值了。F能够是别的一般Monad。在上头的例子就形成了:

OptionT[Either,A](run:
Either[Option[A]]),这个Either[Option[A]]正是大家的对象项目。而笔者辈在操作时如在for-comprehension中运算时利用的体系则必需联合为OptionT[Either,A]。

大家怎么去创设Monad
Transformer类型值呢?我们能够用Applicative[MT].point也许直接用创设器格局如OptionT(…)

//point升格
Applicative[Result].point(62)                     //> res0: Exercises.monadtxns.Result[Int] = OptionT(\/-(Some(62)))
//简写版本
62.point[Result]                                  //> res1: Exercises.monadtxns.Result[Int] = OptionT(\/-(Some(62)))
//会产生错误结果
None.point[Result]                                //> res2: Exercises.monadtxns.Result[None.type] = OptionT(\/-(Some(None)))
"Oh,shit!".left.point[Result]                     //> res3: Exercises.monadtxns.Result[scalaz.\/[String,Nothing]] = OptionT(\/-(So
                                                  //| me(-\/(Oh,shit!))))
//用构建器
OptionT((None: Option[Int]).point[Error])         //> res4: scalaz.OptionT[Exercises.monadtxns.Error,Int] = OptionT(\/-(None))
OptionT(none[Int].point[Error])                   //> res5: scalaz.OptionT[Exercises.monadtxns.Error,Int] = OptionT(\/-(None))
OptionT("Oh,shit!".left: Error[Option[Int]])      //> res6: scalaz.OptionT[Exercises.monadtxns.Error,Int] = OptionT(-\/(Oh,shit!))

与重新构建另二个类型分歧的是,通过Monad
Transformer叠合Monad组合形成门类的操作依旧选用各组成Monad的操作函数,那一个函数运算结果类型任然是对应的Monad类型,所以须要一些升高函数(lifting
functions)来归并类型。而重新塑造项目则持续了整合Monad的操作函数,它们的演算结果类型都与新建的这些连串一致。上边大家依旧用地点的那些Either+Option例子来演示。大家把Either和Option叠合后依照区别顺序能够生出Either[Option[A]]或者Option[Either[A]]二种结果类型,所以叠加顺序是老大首要的,因为那三种档案的次序代表着完全不一样的含义:Either[Option[A]]表示三个运算结果能够是水到渠成right或然失利left,要是运算成功则赶回八个结出或空值;而Option[Either[A]]从字面上领会好疑似二个运算可以回来三个打响或停业的演算又或许重返空值,应该是不曾其余意义的叁个类型。前边大家提到过用Monad
Transformer叠加Monad是由内向外反方向的:获取Either[Option[A]]就需求用OptionT[Either,A]。并且大家必要把Either和Option升格成OptionT[Either,A],看上边包车型客车身体力行:

 1 type Error[A] = \/[String, A]
 2 type Result[A] = OptionT[Error, A]
 3 
 4 def getString: Option[String] = "Hello ".some     //> getString: => Option[String]
 5 def getResult: Error[String] = "how are you!".right
 6                                                   //> getResult: => Exercises.monadtxns.Error[String]
 7 val prg: Result[String] = for {
 8   s1 <- OptionT.optionT(getString.point[Error])
 9   s2 <- "World,".point[Result]
10   s3 <- getResult.liftM[OptionT]
11 } yield s1+s2+s3                                  //> prg  : Exercises.monadtxns.Result[String] = OptionT(\/-(Some(Hello World,how
12                                                   //|  are you!)))
13 prg.run                                           //> res0: Exercises.monadtxns.Error[Option[String]] = \/-(Some(Hello World,how a
14                                                   //| re you!))

首先,大家幸免了stair-stepping,直接运算s1+s2+s3。point、OptionT.optionT、liftM分别对String,Option,Either进行项目升格造成Result[String]
>>> OptionT[Error,String]。升格函数源代码如下:

trait ApplicativeIdV[A] extends Ops[A] {
    def point(implicit F: Applicative[F]): F[A] = Applicative[F].point(self)
...
trait OptionTFunctions {
  def optionT[M[_]] = new (({type λ[α] = M[Option[α]]})#λ ~> ({type λ[α] = OptionT[M, α]})#λ) {
    def apply[A](a: M[Option[A]]) = new OptionT[M, A](a)
  }
...
final class MonadOps[F[_],A] private[syntax](val self: F[A])(implicit val F: Monad[F]) extends Ops[F[A]] {
  ////

  def liftM[G[_[_], _]](implicit G: MonadTrans[G]): G[F, A] = G.liftM(self)
...

再看看组合的Monad是不是完结了成效叠合,假如大家加个None调换:

1 val prg: Result[String] = for {
2   s1 <- OptionT.optionT(getString.point[Error])
3   s0 <- OptionT(none[String].point[Error])  
4   s2 <- "World,".point[Result]
5   s3 <- getResult.liftM[OptionT]
6 } yield s1+s2+s3                                  //> prg  : Exercises.monadtxns.Result[String] = OptionT(\/-(None))
7 prg.run                                           //> res0: Exercises.monadtxns.Error[Option[String]] = \/-(None)

加个Left效果:

1 val prg: Result[String] = for {
2   s1 <- OptionT.optionT(getString.point[Error])
3   s0 <- OptionT("Catch Error!".left: Error[Option[String]])
4   s2 <- "World,".point[Result]
5   s3 <- getResult.liftM[OptionT]
6 } yield s1+s2+s3                                  //> prg  : Exercises.monadtxns.Result[String] = OptionT(-\/(Catch Error!))
7 prg.run                                           //> res0: Exercises.monadtxns.Error[Option[String]] = -\/(Catch Error!)

当真,用Monad
Transformer组合Monad后得以兑现有员Monad的职能叠合。

只是,在实际应用中两层以上的Monad组合照旧比较广泛的。Monad
Transformer本人正是Monad,能够承接与另二个Monad组合,只要用这几个Monad的Transformer就行了。举例大家在上头的例证里再追加一层State,最后产生一个三层类型:State[Either[Option[A]]]。依照地点的阅历,堆砌Monad是由内向外的,我们先组合
StateEither >>>
StateT[Either,A],然后再得出组合:OptionT[StateEither,A]。我们来演示一下:

先重新命名(alias)一些类:

type StringEither[A] = String \/ A
type StringEitherT[M[_],A] = EitherT[M,String,A]
type IntState[A] = State[Int,A]
type IntStateT[M[_],A] = StateT[M,Int,A]
type StateEither[A] = StringEitherT[IntState,A]
type StateEitherOption[A] = OptionT[StateEither,A]

由Option,Either,State组合而成的Monad必要有关的进级函数(lifting
functions):

//常量升格
val m: StateEitherOption[Int] = 3.point[StateEitherOption]
                                                  //> m  : Exercises.monad_txnfm.StateEitherOption[Int] = OptionT(EitherT(scalaz.p
                                                  //| ackage$StateT$$anon$1@4f638935))
//option类升格
val o: Option[Int] = 3.some                       //> o  : Option[Int] = Some(3)
val o1: StateEither[Option[Int]]= o.point[StateEither]
                                                  //> o1  : Exercises.monad_txnfm.StateEither[Option[Int]] = EitherT(scalaz.packag
                                                  //| e$StateT$$anon$1@694abbdc)

val o2: StateEitherOption[Int] = OptionT.optionT(o1)
                                                  //> o2  : Exercises.monad_txnfm.StateEitherOption[Int] = OptionT(EitherT(scalaz.
                                                  //| package$StateT$$anon$1@694abbdc))
//val o2: OptionT[StateEither,Int] = OptionT.optionT(o1)

//either类升格
val e: StringEither[Int] = 3.point[StringEither]  //> e  : Exercises.monad_txnfm.StringEither[Int] = \/-(3)
val e1: IntState[StringEither[Int]] = e.point[IntState]
                                                  //> e1  : Exercises.monad_txnfm.IntState[Exercises.monad_txnfm.StringEither[Int]
                                                  //| ] = scalaz.package$StateT$$anon$1@52bf72b5
val e2: StateEither[Int] = EitherT.eitherT(e1)    //> e2  : Exercises.monad_txnfm.StateEither[Int] = EitherT(scalaz.package$StateT
                                                  //| $$anon$1@52bf72b5)
//val e2: StringEitherT[IntState,Int] = EitherT.eitherT(e1)
val e3: StateEitherOption[Int] = e2.liftM[OptionT]//> e3  : Exercises.monad_txnfm.StateEitherOption[Int] = OptionT(EitherT(scalaz.
                                                  //| IndexedStateT$$anon$10@2d7275fc))
//val e3: OptionT[StateEither,Int] = e2.liftM[OptionT]
//state类升格
val s: IntState[Int] = get[Int]                   //> s  : Exercises.monad_txnfm.IntState[Int] = scalaz.package$State$$anon$3@7e0
                                                  //| 7db1f
val s1: StateEither[Int] = s.liftM[StringEitherT] //> s1  : Exercises.monad_txnfm.StateEither[Int] = EitherT(scalaz.IndexedStateT
                                                  //| $$anon$10@8f4ea7c)
//val s1: StringEitherT[IntState,Int] = s.liftM[StringEitherT]
val s2: StateEitherOption[Int] = s1.liftM[OptionT]//> s2  : Exercises.monad_txnfm.StateEitherOption[Int] = OptionT(EitherT(scalaz
                                                  //| .IndexedStateT$$anon$10@436813f3))
//val s2: OptionT[StateEither,Int] = s1.liftM[OptionT]
//把State升格成StateT
val s3: IntStateT[StringEither,Int] = get[Int].lift[StringEither]
                                                  //> s3  : Exercises.monad_txnfm.IntStateT[Exercises.monad_txnfm.StringEither,In
                                                  //| t] = scalaz.IndexedStateT$$anon$7@10e31a9a

上面又多介绍了StateT.lift,
EitherT.eitherT七个进级函数:

  def lift[M[_]: Applicative]: IndexedStateT[({type λ[α]=M[F[α]]})#λ, S1, S2, A] = new IndexedStateT[({type λ[α]=M[F[α]]})#λ, S1, S2, A] {
    def apply(initial: S1): M[F[(S2, A)]] = Applicative[M].point(self(initial))
  }
...
trait EitherTFunctions {
  def eitherT[F[_], A, B](a: F[A \/ B]): EitherT[F, A, B] = EitherT[F, A, B](a)
...

我们在地方例子的功底上扩大一层State效果后再试用一下那些升高函数:

 1 def getString: Option[String] = "Hello ".some     //> getString: => Option[String]
 2 def getResult: StringEither[String] = "how are you!".right[String]
 3                                                   //> getResult: => Exercises.monad_txnfm.StringEither[String]
 4 def modState(s:Int): IntState[Unit] = put(s)      //> modState: (s: Int)Exercises.monad_txnfm.IntState[Unit]
 5 val prg: StateEitherOption[String] = for {
 6   s1 <- OptionT.optionT(getString.point[StateEither])
 7   s2 <- "World,".point[StateEitherOption]
 8   s3 <- (EitherT.eitherT(getResult.point[IntState]): StateEither[String]).liftM[OptionT]
 9   _ <- (modState(99).liftM[StringEitherT]: StateEither[Unit]).liftM[OptionT]
10 } yield s1+s2+s3                                  //> prg  : Exercises.monad_txnfm.StateEitherOption[String] = OptionT(EitherT(sc
11                                                   //| alaz.IndexedStateT$$anon$10@158d2680))
12 prg.run                                           //> res0: Exercises.monad_txnfm.StateEither[Option[String]] = EitherT(scalaz.In
13                                                   //| dexedStateT$$anon$10@158d2680)

正确,类型对了,prg能够经过编写翻译,但未免复杂了点。笔者花了多数时光去匹配那一个品种,因为急需接二连三升高。综上说述,假使蒙受四层以上的Monad组合,代码会复杂成什么样。个中主要照旧在各体系型的升迁。那我们还是回看一下那一个提高函数吧:

 

A.point[F[_]]
>>> F[A]   “hi”.point[Option] = Option[String] =
Some(“hi”)

 

M[A].liftM[T[_[_],_]]
>>> T[M,A]   List(3).liftM[OptionT] = OptionT[List,Int] =
OptionT(List(Some(3)))

 

OptionT.optionT(M[Option[A]])
>>> OptionT[M,A]  OptionT.optionT(List(3.some)) =
OptionT[List,Int] = OptionT(List(Some(3)

 

EitherT.eitherT(M[Either[A]])
>>> EitherT[M,A] EitherT.eitherT(List(3.right[String])) =
EitherT(List(\/-(3))

 

State.lift[M[A]] >>>
StateT[M,A]  get[Int].lift[Option] =
StateT[Option,Int]

 

潜心:以上选取了影像类型表述

 

 

 

 

 

 

 

 

 

 

相关文章