Scalaz(25)- Monad: Monad Transformer-叠加Monad效果
? 中間插播了幾篇scalaz數(shù)據(jù)類型,現(xiàn)在又要回到Monad專題。因?yàn)镕P的特征就是Monad式編程(Monadic programming),所以必須充分理解認(rèn)識(shí)Monad、熟練掌握Monad運(yùn)用。曾經(jīng)看到一段對(duì)Monad的描述:“Monadic for-comprehension就是一種嵌入式編程語言,由它的Monad提供它的語法”。但如果每一種Monad的for-comprehension都獨(dú)立提供一套語法的話,這種編程語言就顯得十分單調(diào)、功能簡(jiǎn)單了。那么既然是FP,我們應(yīng)該可以通過函數(shù)組合(function composition)來把很多簡(jiǎn)單的for-comprehension組合成一套完善的編程語言吧?比如這樣:Option[A] >>> IO[Option[A]] >>> IO[Either[String,Option[A]]。恰恰,Monad是不支持函數(shù)組合的。先了解一下函數(shù)組合:Functor是可以組合的,我們可以把fa和fb組合成一個(gè)更復(fù)雜的Functor fab,我們來驗(yàn)證一下:
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來運(yùn)算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組合應(yīng)該是這樣的:M[N[A]],M,N都是Monad,如:Either[String,Option[A]],甚至是M[N[P[A]]],三層Monad。可惜,不是所有Monad都支持函數(shù)組合的,看下面:
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]] =??? ...}實(shí)現(xiàn)M[N[A]].bind是不可能的,大家可以試試。這就堵死了函數(shù)組合這條路。難道我們就無法使用M[N[A]]這樣的for-comprehension了嗎?畢竟像Either[String,Option[A]]這樣的組合是很普遍的啊,比如說從數(shù)據(jù)庫里讀取這樣的動(dòng)作,有幾種可能:取得數(shù)據(jù)、無數(shù)據(jù)None、發(fā)生錯(cuò)誤。無論如何我們先試試用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才能運(yùn)算A值。那么可想而知如果是M[N[P[A]]]就需要三層for-comprehension了。這就是所謂的“下階梯式算法”(stair-stepping)。表面上來看stair-stepping會(huì)產(chǎn)生復(fù)雜臃腫的代碼,喪失FP的精簡(jiǎn)優(yōu)雅風(fēng)格。但想深一層,如果其中一個(gè)Monad是會(huì)產(chǎn)生副作用的如IO[Option[A]],那么上面的例子就變成這樣:
1 for { 2 optionData <- IO 3 } yield { 4 for { 5 data <- optionData 6 } yield Process(data) 7 }我們看到在第一層運(yùn)算里進(jìn)行了IO運(yùn)算,產(chǎn)生了副作用。那么以上的代碼就不再是純代碼了,無法保障函數(shù)組合。也就是說stair-stepping會(huì)產(chǎn)生不純代碼,違背了FP要求。之前我們?cè)?jīng)討論過?ReaderWriterState?Monad,它是Reader,Writer,State三個(gè)Monad的組合。在它的for-comprehension里的運(yùn)算結(jié)果類型是ReaderWriterState一種,所以沒有stair-stepping憂慮。但我們必須先創(chuàng)建一個(gè)新的類型(不是通過函數(shù)組合的新類型)。難道我們?cè)谑褂貌煌蟮膄or-comprehension時(shí)都需要重新創(chuàng)建一個(gè)新類型嗎,這樣不就損失了FP的代碼重復(fù)使用特點(diǎn)了嗎?不,scalaz提供的Monad Transformer就是一個(gè)有效的解決方案。
scalaz為很多type class提供了Monad Transformer,它們都以T尾綴命名如OptionT、EitherT、StateT...,我們可以通過Monad Transformer來靈活地組合Monad。以O(shè)ptionT為例:
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)))現(xiàn)在,運(yùn)算A只需要一層context了。Result就是通過Monad Transformer產(chǎn)生的新類型。在上面的類型構(gòu)建里,OptionT就是一個(gè)Monad Transformer、Error是固定了Left類型的Either。因?yàn)镋ither有兩個(gè)類型參數(shù),我們實(shí)際上也可以直接用type lambda來表示Result[A]:
type Result[A] = OptionT[({type l[x] = \/[String,x]})#l,A]不過這樣寫不但復(fù)雜,而且會(huì)影響編譯器的類型推導(dǎo)(compiler type inference)。
值得注意的是,Monad Transformer 類型的構(gòu)建是由內(nèi)向外反向的。比如上面的例子中OptionT是個(gè)Monad Transformer,它的類型款式是OptionT[M[_],A]。OptionT實(shí)際上是用來構(gòu)建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 的主要作用就在構(gòu)成run這個(gè)我們稱為嵌入值了。F可以是任何普通Monad。在上面的例子就變成了:
OptionT[Either,A](run: Either[Option[A]]),這個(gè)Either[Option[A]]就是我們的目標(biāo)類型。而我們?cè)诓僮鲿r(shí)如在for-comprehension中運(yùn)算時(shí)使用的類型則必須統(tǒng)一為OptionT[Either,A]。
我們?nèi)绾稳?gòu)建Monad Transformer類型值呢?我們可以用Applicative[MT].point或者直接用構(gòu)建器方式如OptionT(...)
//point升格 Applicative[Result].point(62) //> res0: Exercises.monadtxns.Result[Int] = OptionT(\/-(Some(62))) //簡(jiǎn)寫版本 62.point[Result] //> res1: Exercises.monadtxns.Result[Int] = OptionT(\/-(Some(62))) //會(huì)產(chǎn)生錯(cuò)誤結(jié)果 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!)))) //用構(gòu)建器 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!))與重新構(gòu)建另一個(gè)類型不同的是,通過Monad Transformer疊加Monad組合形成類型的操作依然使用各組成Monad的操作函數(shù),這些函數(shù)運(yùn)算結(jié)果類型任然是對(duì)應(yīng)的Monad類型,所以需要一些升格函數(shù)(lifting functions)來統(tǒng)一類型。而重建類型則繼承了組成Monad的操作函數(shù),它們的運(yùn)算結(jié)果類型都與新建的這個(gè)類型一致。下面我們還是用上面的這個(gè)Either+Option例子來示范。我們把Either和Option疊加后按照不同順序可以產(chǎn)生Either[Option[A]]或者Option[Either[A]]兩種結(jié)果類型,所以疊加順序是非常重要的,因?yàn)檫@兩種類型代表著截然不同的意義:Either[Option[A]]代表一個(gè)運(yùn)算結(jié)果可以是成功right或者失敗left,如果運(yùn)算成功則返回一個(gè)結(jié)果或空值;而Option[Either[A]]從字面上理解好像是一個(gè)運(yùn)算可以返回一個(gè)成功或失敗的運(yùn)算又或者返回空值,應(yīng)該是沒有任何意義的一個(gè)類型。前面我們提到過用Monad Transformer疊加Monad是由內(nèi)向外反方向的:獲取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,直接運(yùn)算s1+s2+s3。point、OptionT.optionT、liftM分別對(duì)String,Option,Either進(jìn)行類型升格形成Result[String] >>> OptionT[Error,String]。升格函數(shù)源代碼如下:
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是否實(shí)現(xiàn)了功能疊加,如果我們加個(gè)None轉(zhuǎn)換:
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)加個(gè)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后可以實(shí)現(xiàn)成員Monad的效果疊加。
不過,在實(shí)際應(yīng)用中兩層以上的Monad組合還是比較普遍的。Monad Transformer本身就是Monad,可以繼續(xù)與另一個(gè)Monad組合,只要用這個(gè)Monad的Transformer就行了。例如我們?cè)谏厦娴睦永镌僭黾右粚覵tate,最終形成一個(gè)三層類型:State[Either[Option[A]]]。按照上面的經(jīng)驗(yàn),堆砌Monad是由內(nèi)向外的,我們先組合 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需要相關(guān)的升格函數(shù)(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兩個(gè)升格函數(shù):
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) ...我們?cè)谏厦胬拥幕A(chǔ)上增加一層State效果后再試用一下這些升格函數(shù):
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)不錯(cuò),類型對(duì)了,prg可以通過編譯,但未免復(fù)雜了點(diǎn)。我花了許多時(shí)間去匹配這些類型,因?yàn)樾枰B續(xù)升格。可想而知,如果遇到四層以上的Monad組合,代碼會(huì)復(fù)雜成怎樣。其中重點(diǎn)還是在各種類型的升格。那我們還是回顧一下這些升格函數(shù)吧:
?
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]
?
注意:以上采用了形象類型表述
?
?
?
?
?
?
?
?
?
?
轉(zhuǎn)載于:https://www.cnblogs.com/tiger-xc/p/5143993.html
總結(jié)
以上是生活随笔為你收集整理的Scalaz(25)- Monad: Monad Transformer-叠加Monad效果的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: myeclipse和输入法冲突的问题
- 下一篇: express细节点注意