您留存的意思,泛函编制程序

   
经过了大器晚成段时间的学习,大家领悟了生机勃勃雨后春笋泛函数据类型。我们领略,在具备编制程序语言中,数据类型是协助软件编制程序的根基。相符,泛函数据类型Foldable,Monoid,Functor,Applicative,Traversable,Monad也是大家以往跻身实际泛函编制程序的丹青妙手。在前边对那个数据类型的研究中大家开采:

前边提到了scalaz是个函数式编制程序工具库。它提供了广大新的数据类型、扩充的标准项目及全部的生龙活虎套typeclass来支撑scala语言的函数式编程情势。大家领略:对于别的项目,大家只供给实现那些项目标typeclass实例就足以在对那些种类应用所对应typeclass提供的保有组件函数了(combinator卡塔尔国。忽然之间大家的规范好像都坐落了哪些赢得typeclass实例上了,进而忽视了考虑怎么要动用这个typeclass及运用什么的typeclass那个难题了。所以大概有人会问小编:怎样获得Int的Monad实例。小编会反问:傻B,你疯了吗(are
you
insane卡塔尔国?你毕竟想干什么?那时傻B只怕忽地会醒来还未有当真了然本身这么问的指标。看来我们依旧回到难点的根源,从使用scalaz的大旨指标开首思谋解析了。

1、Monoid的重要用场是在拓宽折叠(Foldable卡塔 尔(阿拉伯语:قطر‎算法时对可折叠结构内成分实行函数施用(function
application卡塔尔国、

我们就围绕scalaz提供的大家都胸中有数的typeclass Functor, Applicative,
Monad来剖析表达呢,因为大家在日前对它们都开展了商讨介绍,为了与scalaz提供的不在少数其余typeclass有所区分,大家就临时把它们统称为Monadic
typeclass吧。首先,那么些Monadic
typeclass不是数据类型,而是意味着着好几编制程序的形式。我们通晓FP编制程序和OOP编制程序最大的不一样正是FP编制程序的境况不可变性(immutability卡塔 尔(英语:State of Qatar)、无副作用(no-side-effect卡塔尔国、纯函数组合工夫(pure
code
composability卡塔尔,那将要求FP编程在某种壳子里开展意况转变(transformation卡塔 尔(阿拉伯语:قطر‎。形象点表明正是F[T]。F[]就算种种诡异的壳子而T正是索要运算调换的某连串型值。FP程序的结果形象描述就疑似F[T]
=> F[U]:
代表在F[]盖子内对T举办演算,并把结果U保存在F[]内。既然FP编制程序相对于OOP编制程序是种崭新的编制程序方式,那么自然必要后生可畏套新的顺序状态调换方式,也正是意气风发套新的操作函数施用情势了。Scalaz通过Functor,
Applicative,
Monad提供了三种基本的函数施用情势,它们都以针对F[T]里的T值:

2、Functor能够对任何高阶数据类型F[_]内的要素进行普通函数(A
=> B卡塔尔施用(map卡塔尔

1 // Functor    :  map[T,U]    (f:   T => U):  F[U]2 // Applicative:  ap[T,U]     (f: F[T => U]): F[U] 3 // Monad      :  flatMap[T,U](f: T => F[U]): F[U]

3、Applicative extends
Functor,相近都是对F[_}内成分举办函数施用。分歧的是应用函数是包嵌在高阶类型的(F[A
=>
B]卡塔 尔(阿拉伯语:قطر‎。Applicative能够对持有可旅游结构(Traversable卡塔尔,包含可折叠结构(Foldable卡塔尔国,嵌入的成分实行函数施用。Applicative好像比Monoid功用更是有力,那样,Applicative的主要用处之一应该是对可畅游结构内元素进行函数施用。

上述函数施用情势发出相通的功用:F[T]
=> F[U],都是第超级的FP编制程序方式。所以能够说Monadic
typeclass提供了正规的FP编制程序框架,程序员能够使用那些框架实行FP编制程序。假如这么表达使用scalaz的目标,是否更明白一些了?

4、Monad应该是泛函编制程序中最要紧的数据类型。Monad
extends Applicative,那样,Monad就包涵了Functor,
Applicative的习性。更要紧的是,Monad成就了for-comprehension。通过for-comprehension能够兑现泛函风格的“行令编制程序形式(imperative
programming)。泛函编制程序与古板的行令编制程序在格局上最大的个别便是在泛函编制程序中并未有变量申明(variable
declaration卡塔 尔(英语:State of Qatar),变量是包嵌在二个构造里的(MyData(data)),得注脚那几个组织(trait
MyData[String])。所以泛函编制程序的吩咐实施都以在有的协会内部实行的。Monad组件库中的组件首要扶助这种组织内部运算风格。不或然利用行令编制程序形式必然对泛函编制程序进程引致不便,但Monad使for-comprehension成为恐怕,而在for-comprehension内足以兑现行反革命令编制程序,所以泛函编制程序被称作Monadic
programming并不为过。

从另叁个角度表达:scalaz
typeclass
代表着抽象编制程序概念。typeclass是通过随机多态来兑现针对各类别型值的FP式总结的。回到起头傻B的主题材料:Int是豆蔻梢头种基本功项目,换句话说正是FP函数施用的靶子。Monadic
typeclass针对的项目是高阶的F[T]花色。大家需要对在F[]的功用条件里T类型值总括方法举办包含。大家的确供给获得的实例实际上是针对性高阶类型F[_]的。所以傻B问了个谬误的主题素材,确定他立时不知自个儿在干什么。

看个for-comprehension例子:

目前大家能够深入分析一下相应选取什么typeclass了。总体来讲,作者的了然是足以把scalaz
typeclass分成连串和特质:

1   val compute: Option[Int] = {
2    for {
3        x <- getNextNumber
4        x1 <- getNextNumber
5        y <- shapeIt(x)
6        z <- divideBy(y,x1)
7    } yield z
8   }

体系定义了FP编制程序的各样情势。比方Functor,
Applicative,
Monad都代表差别的编制程序格局也许说它们都具有分化的顺序运算形式。特质是指不相同的数据类型所定义的typeclass实例调节着程序的切实运算行为。如Option
Monad可以None状态中途结束运算、State
Monad确认保障状态值平昔随着程序运算。它们都因为依照不一样门类的实例而表现各异的演算行为。Functor,
Applicative, Monad的特质则由它们的实例中map, ap,
flatMap那八个驱动函数的实际完成格局所决定。我们先看看现存的Option
Functor,它的得以落成情势如下:

程序在for{}内一步一步运维,标准的行令情势。

1 mplicit object optionFunctor extends Functor[Option] {2     def map[T,U](ot: Option[T])(f: T => U): Option[U] = ot match {3         case Some => Some4         case None => None5     }6 }

能够说:for-comprehension组成了三个嵌入式的简要行令编制程序语言,而发生它的Monad同不常候又鲜明了它的语意(symatics)。

Option
Functor实例驱动函数map的意思是说只要指标类型F[T]的值是个Some,那么我们就在Some壳Nelly用参数提供的貌似函数f;借使目的值是None就不选择函数。我们再看看List
Functor:

上述的例证中for-comprehension是由Option[Int]概念的,那么,借使那些for-comprehension是由三个以上Monad组成的啊?比如:IO[Option[A]],那几个有一些像组合(Monad
composition卡塔尔。那么大家就先从Monad composition初始吧,看怎么把三个Monad
compose起来。

1 implicit object listFunctor extends Functor[List] {2     def map[T,U](lt: List[T])(f: T => U): List[U] = lt match {3         case Nil => Nil4         case head :: tail => f :: map5     }6 }

怎么compose呢?先看看Functor的composition:

List
Functor的map函数展现出对大器晚成串在壳内成分每个转换的特征。从List操作方法就超级轻巧驾驭:list.map(t
=> transform

 1     trait Functor[F[_]] {
 2         def map[A,B](fa: F[A])(f: A => B): F[B]
 3     }
 4   def compose[F[_],G[_]](m: Functor[F], n: Functor[G]) =
 5       new Functor[({type l[x] = F[G[x]]})#l] {
 6           override def map[A,B](fga: F[G[A]])(f: A => B) = {
 7               m.map(fga)(ga => n.map(ga)(f))
 8           }
 9       }                                           //> compose: [F[_], G[_]](m: ch12.ex2.Functor[F], n: ch12.ex2.Functor[G])ch12.e
10                                                   //| x2.Functor[[x]F[G[x]]]

大家再看看Option
Applicative的实例:

大家知道:只要完毕了指雁为羹函数map,就足以变成Functor实例。那一个Functor[({type l[x]
= F[G[x]]})#l]正是多少个Functor实例,因为我们得以完毕map[A,B](fga:
F[G[A]])(f: A =>
B)。有了那几个Functor实例,大家就能够管理F[G[A]]那样类型的数据类型:

1 implicit object objectApplicative extends Applicative[Option] {2     def point[T]: Option[T] = Some3     def ap[T,U](ot: Option[T])(of: Option[T => U]): Option[U] =  match {4         case , Some => Some5         case _ => None 6     }7 }
 1  val listFunctor = new Functor[List] {
 2       override def map[A,B](la: List[A])(f: A => B): List[B] = la.map(f)
 3   }                                               //> listFunctor  : ch12.ex2.Functor[List] = ch12.ex2$$anonfun$main$1$$anon$6@3c
 4                                                   //| bbc1e0
 5   val optionFunctor = new Functor[Option] {
 6       override def map[A,B](oa: Option[A])(f: A => B): Option[B] = oa.map(f)
 7   }                                               //> optionFunctor  : ch12.ex2.Functor[Option] = ch12.ex2$$anonfun$main$1$$anon$
 8                                                   //| 7@35fb3008
 9   
10   Option("abc").map(_.length)                     //> res4: Option[Int] = Some(3)
11   val fg = compose(listFunctor,optionFunctor)     //> fg  : ch12.ex2.Functor[[x]List[Option[x]]] = ch12.ex2$$anonfun$main$1$$anon
12                                                   //| $5@7225790e
13   
14   fg.map(List(Option("abc"),Option("xy"),Option("ryuiyty"))){ _.length }
15                                                   //> res5: List[Option[Int]] = List(Some(3), Some(2), Some(7))

Option
Applicative的驱动函数ap又一次显示了Option的特意管理情势:独有在指标值和操作函数都不为None时才使用通过壳提供的操作函数。

 在上述大家用listFunctor管理了List[A]花色数据,optionFunctor处理Option[A]。最后大家用fg管理像List[Option[String]]品种的数量。

再看看Option
Monad实例:

 那么我们只要能促成Monad[M[N]]的flatMap不就能够获取那几个Monad实例了呗:

1 mplicit object optionMonad extends Monad[Option] {2     def flatMap[T,U](ot: Option[T])(f: T => Option[U]): Option[U] = ot match {3         case Some => f4         case _ => None5     }6 }

 

其风流洒脱flatMap函数能够告知我们越来越多东西:假若咱们把Option[T]用作多个运算的话,那么风流倜傥旦这些运算结果不为None就足以选取总是运算,因为:f:
T =>
Option[U],用文字描述即为给二个T值举办总计后发生另五个运算Option[U],倘诺再给Option[U]多少个值进行估测计算的话就又会爆发另多个运算Opton[V]…
如此持续:

1   def composeM[M[_],N[_](m: Monad[M], n: Monad[N]): Monad[({type l[x] = M[N[x]]})#l]= {
2       new Monad[({type l[x] = M[N[x]]})#l] {
3           def flatMap[A,B](mna: M[N[A]])(f: A => M[N[B]]): M[N[B]] = {
4             ????? !!!!!
5           }
6       }
7   } 

F[A](a =>
F[B](b => F[C](c => F[D])…))。用flatMap链表示:

 

1  fa.flatMap(a => fb.flatMap(b => fc.flatMap(c => fd.map

难受的是本次无法兑现flatMap。这些喜剧分明了推论“Monad
do not
compose!”。那我们的Monadic语言梦想就这样快幻灭了啊?实际上八个Monad定义的for-comprehension能够经过Monad
Transformer来实现。Monad
Transformer可以兑现五个Monad效果的增进(stacking
effect卡塔 尔(阿拉伯语:قطر‎。好,那大家就起来看看那些Monad Transformer吧:

从flatMap串联就比较轻巧观望到Monad运算的关系依赖性和串联行:后边多少个运算供给前边那些运算的结果。而在Option
Monad里假使前方的演算爆发结果是None的话,串联运算终止并直接重返None作为整串运算的结果。

笔者们先完毕多少个Maybe
Monad:

值得一说醒的是种类的flatMap其实也是风度翩翩种递归算法,但又不归属尾递归,所以具备和别的FP算法相似的破绽:会消耗饭店,超长的flatMap链条非常轻巧以致货仓溢出错误(stack
overflow卡塔 尔(阿拉伯语:قطر‎。所以,直接运用Monad编制程序是不安全的,必须与Trampling数据结构合营使用才行。正确安全的Monad使用方法是通过Trampling结构贮存原来在仓房上的函数调用参数,以heap替换stack来制止stack-overflow。大家会在前天详细商量Trampling原理机制。

Maybe便是Option。由于scala标准Curry已经有Option类型,为免函数引用混扰,所以定义多少个新的Monad。

大家得以从地点的flatMap串中国对外演出公司绎出for-comprehension:

 1     trait Functor[F[_]] {
 2         def map[A,B](fa: F[A])(f: A => B): F[B]
 3     }
 4     trait Monad[M[_]] extends Functor[M] {
 5         def unit[A](a: A): M[A]
 6         def flatMap[A,B](ma: M[A])(f: A => M[B]): M[B]
 7     }    
 8     trait Maybe[+A] {
 9         def map[B](f: A => B): Maybe[B] ={
10             this match {
11                 case Just(a) => Just(f(a))
12                 case _ => Nada
13             }
14         }
15         def flatMap[B](f: A => Maybe[B]): Maybe[B] = {
16             this match {
17                 case Just(a) => f(a)
18                 case _ => Nada
19             }
20         }
21     }
22     case class Just[+A](a: A) extends Maybe[A]
23     case object Nada extends Maybe[Nothing]
1 //   for {2 //      a <- 3 //      b <- 4 //      c <- 5 //   } yield { ... }

大家兑现了Maybe类型的unit,map,flatMap,所以我们能够在Maybe
Monad的条件里福寿年高for-comprehension的接受

从for-comprehension能够更便于看见:大家得以选用在for
loop内按供给连续运算F[T]。只要我们能提供a,b,c …作为运算成分。

 

按说来讲除了Option
Monad,此外类其余Monad都具备这种连接运算的可接纳性。而Option
Monad的特色就在于在运算结果为None时得以立时停下运算。

1     val maybeFor: Maybe[Int] = for {
2         x <- Just(2)
3         y <- Just(5)
4         z = x * y
5     } yield z                                 //> maybeFor  : ch12.ex2.Maybe[Int] = Just(10)

明日大家得以试着自定义贰个品种然后拿走个如何实例。然则大家依然要谨记自定义类型的指标何在。笔者看许多只怕是落到实处Monad实例,那样我们就能够在自定义类型的操纵下打开Monadic编制程序了,即在for-comprehension内打开熟习的行令编制程序(imperative
programming卡塔 尔(英语:State of Qatar)。我们应当没什么供给去得到Functor或Applicative实例,何况Monad
trait也世袭了Functor及Applicative
trait,因为map和ap都能够用flatMap来达成:

 

1 ef map[A,B](f: A => B): F[B] =2   fa flatMap {a => point}3 def ap[A,B](ff: F[A => B]): F[B] =4   ff flatMap { f => fa flatMap {a => point }}

大家看见了生机勃勃段嵌在for-comprehension内的行令运算。但运算的情形供给从外表上还不能够不在话下。那么,这段运算的另二个版本恐怕具有启发:

值得注意的是:flatMap有着很扎眼的串性,相符于运算流程处理。但落到实处相互作用运算就能够困难了。那就是Applicative存在的基本点缘由。倘使自定义Monad需求开展交互作用运算的话将在防止用flatMap达成ap。正确的情势是不用任何的机件函数,直接单独实现ap函数。

1     val maybeMap: Maybe[Int] = {
2         Just(2).flatMap(x => Just(5).map(y => x * y))
3                                                   //> maybeMap  : ch12.ex2.Maybe[Int] = Just(10)
4     }

无尽个人自定义Monad恐怕正是粗略希望能用for-comprehension。它是风华正茂种容易的FP编程语言(Monadic
language卡塔 尔(阿拉伯语:قطر‎:能在二个自定义类型的壳内张开发银行令编制程序来兑现程序状态调换。如下边强调的那么,大家必需先要搞明白自定义Monad类型的指标:一发端大家期待能用FP格局得以完结部分粗略的行令编制程序,如下:

大家知道for-comprehension就是flatMap的方法糖。所以上述正是原始flatMap运算。从这么些flatMap表明格局大家能够得出每一句运算都必须要比执照主人导Monad的flatMap函数类型(signature卡塔尔,也正是说类型必需合作。

1 var a = 32 var b = 43 var c = a + b

小编们再来三个熟识的Monad,State
Monad:

正是那般轻巧。可是大家意在用FP格局来兑现。那么可不得以这么描述须要:对雷同某风姿罗曼蒂克种种数据类型的变量举行赋值,然后对那一个变量实践操作,在这里处是相加操作。那么大家供给一个高阶类型F[T],用F来包嵌风流浪漫种等级次序数据T。在壳内运算T后结果或许叁个T类型值。

 

大家先定义一下以此类型吧:

 1     type State[S,+A] = S => (A,S)
 2   object State {
 3       def getState[S]: State[S,S] = s => (s,s)
 4       def setState[S](s: S): State[S,Unit] = _ => ((),s)
 5   }
 6     class StateOps[S,A](sa: State[S,A]) {
 7         def unit(a: A) = (s: S) => (a,s)
 8         def map[B](f: A => B): State[S,B] = {
 9             s => {
10                 val (a,s1) = sa(s)
11                 (f(a),s1)
12             }
13         }
14         def flatMap[B](f: A => State[S,B]): State[S,B] = {
15             s => {
16                 val (a,s1) = sa(s)
17                 f(a)(s1)
18             }
19         }
20     def getState[S]: State[S,S] = s => (s,s)
21     def setState[S](s: S): State[S,Unit] = _ => ((),s)
22     }
23     implicit def toStateOps[S,A](sa: State[S,A]) = new StateOps(sa)
24                                                   //> toStateOps: [S, A](sa: ch12.ex2.State[S,A])ch12.ex2.StateOps[S,A]
1 trait Bag[A] {2     def content: A3 }4 object Bag {5     def apply[A] = new Bag[A] { def content = a }6 }

 

印象点解释:多个口袋Bag里装生龙活虎种能够是此外类型A的东西。

相通大家能够用State
Monad定义的for-comprehension进行行令编制程序:

用scalaz来完结Bag类型的Monad实例很简短:

 

 1 rait Bag[A] { 2     def content: A 3 } 4 object Bag { 5     def apply[A] = new Bag[A] { def content = a } 6     implicit object bagMonad extends Monad[Bag] { 7         def point[A](a: => A) = Bag 8         def bind[A,B](ba: Bag[A])(f: A => Bag[B]): Bag[B] = f(ba.content) 9     }10 }
 1     import State._
 2     val stateFor: State[Int, Int] = for {
 3         x <- getState[Int]
 4         y = x * 5
 5         _ <- setState(x+1)
 6     } yield y                                 //> stateFor  : ch12.ex2.State[Int,Int] = <function1>
 7     
 8     
 9     stateFor(2)                               //> res0: (Int, Int) = (10,3)
10 
11 可以肯定这个State Monad for-comprehension内的行令运算同样需要遵循State Monad map, flatMap的类型匹配。

只要定义了point,bind函数就能够。point能把一个普通类型A的值套入壳子Bag。bind既是flatMap,它调整了从叁个运算连接到下一个运算进程中对壳中数量开展的附加管理。能够观望以上bagMonad的bind函数未有增大其余管理,直接对指标壳内数据(ba.content卡塔 尔(阿拉伯语:قطر‎施用传入函数f。

 

近来Bag已然是个Monad实例了,大家得以应用具备Monad
typeclass提供的函数:

能够分明这个State
Monad for-comprehension内的行令运算相像必要根据State Monad map,
flatMap的品类相配。

 1 val chainABC = Bag(3) flatMap {a => Bag(4) flatMap {b => Bag(5) flatMap  {c => Bag(a+b+c) }}} 2                                                   //> chainABC  : Exercises.monad.Bag[Int] = Exercises.monad$Bag$$anon$1@c8e4bb0 3 chainABC.content                                  //> res0: Int = 12 4  5 val bagABC = Bag(3) >>= {a => Bag(4) >>= {b => Bag(5) map {c => (a+b+c) }}} 6                                                   //> bagABC  : Exercises.monad.Bag[Int] = Exercises.monad$Bag$$anon$1@29626d54 7 bagABC.content                                    //> res1: Int = 12 8 val bagHello = Bag("Hello") >>= {a => Bag(" John,") >>= {b => Bag("how are you?") map {c => (a+b+c) }}} 9                                                   //> bagHello  : Exercises.monad.Bag[String] = Exercises.monad$Bag$$anon$1@5a63f510                                                   //| 0911 bagHello.content                                  //> res2: String = Hello John,how are you?

那大家下边把这多个Monad在一个for-comprehension里运转。譬喻

静心大家是怎么样把壳内变量a,b,c早先方传导到背后的加法操作里的。大家已经贯彻了Monad的流程式运算。
以往大家能够运用最盼望用的for-comprehension来贯彻地点的行令编制程序了:

 1  val nocompileFor = {
 2       def remainder(a: Int, b: Int): Maybe[Int] = {
 3           a % b match {
 4               case 0 => Nada
 5               case r => Just(r)
 6           }
 7       }
 8       for {
 9           x <- getState[Int]    //State.flatMap
10           y <- remainder(x,2)   //Maybe.flatMap
11           z = x + y             //???.map
12           _ <- setState[Int](5) //State.flatMap
13       } yield y
14   }                                               
 1 val addABC: Bag[Int] = for { 2   a <- Bag(3) 3   b <- Bag(4) 4   c <- Bag(5) 5 } yield a+b+c                                     //> addABC  : Exercises.monad.Bag[Int] = Exercises.monad$Bag$$anon$1@10e41621 6 addABC.content                                    //> res2: Int = 12 7  8 val concatABC: Bag[String] = 9 for {10   a <- Bag("hello")11   b <- Bag(" jonh,")12   c <- Bag("how are you ?")13 } yield                                   //> concatABC  : Exercises.monad.Bag[String] = Exercises.monad$Bag$$anon$1@353d014                                                   //| 77215 concatABC.content                                 //> res3: String = hello jonh,how are you ?

能够看的出来,flatMap的品类都乱了套了。以上例子不可能通过编写翻译器。

不用看上边包车型大巴次序好像很简短,但它象征的意思却是重大的:首先我们落到实处了FP格局的图景调换:大家固然选用了行令编制程序,但结尾壳Bag内部的数量content运算结果便是大家编制程序时所企望的。再便是大家透过flatMap串联持续对四个变量意气风发豆蔻梢头实行了赋值,然后用日常的函数把那么些变量进行了结合yield
。能够说大家初叶尝试完结了FP编制程序格局(在一个什么样壳内开展览演出算卡塔 尔(英语:State of Qatar)。

减轻方案:Monad
Transformer:

日前说过,for-comprehension能够是一种简易的FP编制程序语言Monadic
language。用它编制的程序运算行为足以受定义它的Monad实例所主宰。那么大家就试着为大家的Bag
Monad扩展某个震慑:

上边的败诉例子是要清除State[Maybe[A]]那种类型的难题。我们就供给三个State
Monad Transformer:

1 trait Bag[+A] {}2 case class Bagged[+A](content: A) extends Bag[A]3 case object Emptied extends Bag[Nothing]
 1  import StateT._
 2     trait StateT[M[_],S,A] {   // State Monad Transformer
 3       def apply(s: S): M[(A,S)]
 4       
 5         def map[B](f: A => B)(implicit m: Functor[M]): StateT[M,S,B] = {
 6             stateT( s => m.map(apply(s)){
 7                 case (a,s1) => (f(a),s1)
 8             })
 9         }
10         
11         def flatMap[B](f: A => StateT[M,S,B])(implicit m: Monad[M]): StateT[M,S,B] = {
12             stateT( s => m.flatMap(apply(s)){
13                 case (a,s1) => f(a)(s1)
14             })
15         }
16         
17     }
18   object StateT {
19       def stateT[M[_],S,A](f: S => M[(A,S)]): StateT[M,S,A] = {
20           new StateT[M,S,A] {
21               def apply(s: S) = f(s)
22           }
23       }
24       def liftM[M[_],S,A](ma: M[A])(implicit m: Monad[M]): StateT[M,S,A] = {
25             stateT(s => m.map(ma)(a => (a, s)))
26       }
27   }

作者们有个别调度了一下Bag类型。今后Bag由三种情景组成:有东西的袋子Bagged和空袋子Emptied。借使希望我们的Monadic程序在遇见Emptied时能像Option
Monad这样登时截至运算并一直回到Emptied结果,大家必需在bind函数里设定这种作为:

 StateT是个State Monad
Transformer,同不时候StateT也是叁个Monad实例,因为大家能够兑现它的flatMap函数。既然StateT是个Monad实例,这我们就可以用StateT来定义它的for-comprehension了:

 1 trait Bag[+A] {} 2 case class Bagged[+A](content: A) extends Bag[A] 3 case object Emptied extends Bag[Nothing] 4  5 object Bag { 6     implicit object bagMonad extends Monad[Bag] { 7         def point[A](a: => A) = Bagged 8         def bind[A,B](ba: Bag[A])(f: A => Bag[B]): Bag[B] = ba match { 9           case Bagged => f10           case _ => Emptied11         }12     }13 }

 

在bind函数里大家用形式相配格局决断输入Bag状态:假设是有装东西的那么像上面的兼顾雷同直接运算f获取下八个Bag状态,要是是空袋子Emptied的话就不做其它运算直接再次来到Emptied。大家前几天得以测量试验一下地点定义的运算:

 1   val maybeState: StateT[Maybe,Int,Int] = {
 2     def getState[S]: StateT[Maybe,S,S] = stateT(s => Just((s,s)))
 3     def setState[S](s: S): StateT[Maybe,S,Unit] = stateT(s1 => Just(((),s)))
 4       def remainder(a: Int, b: Int): Maybe[Int] = {
 5           a % b match {
 6               case 0 => Nada
 7               case r => Just(r)
 8           }
 9       }
10       for {
11           x <- getState[Int]
12           y <- liftM[Maybe,Int,Int](remainder(x,2))
13           z = x + y
14           _ <- setState[Int](5)
15       } yield y
16   }                                               //> maybeState  : ch12.ex2.StateT[ch12.ex2.Maybe,Int,Int] = ch12.ex2$$anonfun$m
17                                                   //| ain$1$StateT$3$$anon$4@34b7bfc0
18  
19   maybeState(1)                                   //> res1: ch12.ex2.Maybe[(Int, Int)] = Just((1,5))
20   maybeState(0)                                   //> res2: ch12.ex2.Maybe[(Int, Int)] = Nada
 1 val chainABC = Monad[Bag].point(3) flatMap {a => Monad[Bag].point(4) flatMap {b => Monad[Bag].point(5) flatMap  {c => Bagged(a+b+c) }}} 2                                                   //> chainABC  : Exercises.monad.Bag[Int] = Bagged 3 val bagABC = Monad[Bag].point(3) >>= {a => Monad[Bag].point(4) >>= {b => Monad[Bag].point(5) map {c => (a+b+c) }}} 4                                                   //> bagABC  : Exercises.monad.Bag[Int] = Bagged 5 val bagHello = Monad[Bag].point("Hello") >>= {a => Monad[Bag].point(" John,") >>= {b => Monad[Bag].point("how are you?") map {c => (a+b+c) }}} 6                                                   //> bagHello  : Exercises.monad.Bag[String] = Bagged(Hello John,how are you?) 7 val addABC: Bag[Int] = for { 8   a <- Monad[Bag].point(3) 9   b <- Monad[Bag].point(4)10   c <- Monad[Bag].point(5)11 } yield a+b+c                                     //> addABC  : Exercises.monad.Bag[Int] = Bagged12 13 val concatABC: Bag[String] =14 for {15   a <- Monad[Bag].point("hello")16   b <- Monad[Bag].point(" jonh,")17   c <- Monad[Bag].point("how are you ?")18 } yield                                   //> concatABC  : Exercises.monad.Bag[String] = Bagged(hello jonh,how are you ?)19                                                   //| 

 以上那些for-comprehension是用StateT[Maybe,Int,Int]来定义的。那么富有在for-comprehension内的表达式右方就非得是StateT类型。上边包车型大巴getState,setState函数结果都是StateT类型,但remainder函数再次来到结果却是Maybe类型。所以大家用liftM把Maybe类型升格到StateT类型。liftM的函数定义如下:

大家得以见到在Bag不是Emptied时,以上这个程序运算行为与上贰个版本的Monad程序还没不一致。可是若是大家扩张了Emptied呢:

1       def liftM[M[_],S,A](ma: M[A])(implicit m: Monad[M]): StateT[M,S,A] = {
2             stateT(s => m.map(ma)(a => (a, s)))
3       }
1 val bagABC = Monad[Bag].point(3) >>= {a => (Bagged(4): Bag[Int]) >>= {b => Monad[Bag].point(5) >>= { c => (Emptied: Bag[Int]) map {c => (a+b+c) }}}}2                                                   //> bagABC  : Exercises.monad.Bag[Int] = Emptied

liftM的效应正是把一个Monad
M[A]晋级成为StateT。上边的事例大家用liftM把Monad
Maybe升格成StateT类型,那样一切for-comprehension内部装有表明式类型都相当了。注意StateT把State
Monad和其余其余一个Monad合起来用:上面包车型客车例子用了Maybe。实际上StateT[M,S,A]里的M能够是Maybe也能够是Option,Either,Validation。。。那我们就足以拿到StateT[Option,Int,Int],StateT[Either,Int,Int]那一个Monad
Transformer并在for-comprehension里呈现这几个整合Monad的魔法。更主要的是StateT是个Monad那么大家得以把它当做任何其余Monad同样与别的Monad结合形成新的Monad
Transformer。

flatMap链条中间现身了Emptied,运算终断,重临Emptied结果。注意上边包车型客车表达情势:

纵然大家须要管理相反的品种:Maybe[State],我们就需求定义MaybeT。大家先看看MaybeT的种类款式:

Monad[Bag].point

 caseclass MaybeT[M[_],A](run:
M[Maybe[A]]) 这是Monad Transformer通用款式

: Bag[Int])

小编们把四头接受的Monad包嵌在参数里:

情趣都是同等的。但Bagged.flatMap那样写是丰硕的,因为Bagged不明明是Bag。

 1     case class MaybeT[M[_],A](run: M[Maybe[A]]) {
 2         def map[B](f: A => B)(implicit m: Functor[M]): MaybeT[M,B] = {
 3             MaybeT[M,B](m.map(run)(a => a map f))
 4         }
 5         def flatMap[B](f: A => MaybeT[M,B])(implicit m: Monad[M]): MaybeT[M,B] = {
 6             MaybeT[M,B](m.flatMap(run) {
 7                 case Just(a) => f(a).run
 8                 case Nada => m.unit(Nada)
 9             })
10         }
11     }

再看看在for-comprehension程序中加多Emptied景况:

假诺用Option作为主旨Monad,那么大家得以设计一个Option的Monad
Transformer OptionT类型:

 1 val addABC: Bag[Int] = for { 2   a <- Monad[Bag].point(3) 3   x <- (Emptied: Bag[Int]) 4   b <- Monad[Bag].point(4) 5   c <- Monad[Bag].point(5) 6 } yield a+b+c                                     //> addABC  : Exercises.monad.Bag[Int] = Emptied 7  8 val concatABC: Bag[String] = 9 for {10   a <- Monad[Bag].point("hello")11   x <- (Emptied: Bag[Int])12   b <- Monad[Bag].point(" jonh,")13   c <- Monad[Bag].point("how are you ?")14 } yield                                   //> concatABC  : Exercises.monad.Bag[String] = Emptied
 1   case class OptionT[M[_],A](run: M[Option[A]]) {
 2       def map[B](f: A => B)(implicit m: Functor[M]): OptionT[M,B] = {
 3            OptionT[M,B](m.map(run)(a => a.map(f)))
 4       }
 5       def flatMap[B](f: A => OptionT[M,B])(implicit m: Monad[M]): OptionT[M,B] = {
 6           OptionT[M,B](m.flatMap(run) {
 7               case Some(a) => f(a).run
 8               case None => m.unit(None)
 9           })
10       }
11   }

没有错,即是大家希望的运算行为。

无论如何,只要大家能够把一齐利用的那八个Monad升格成靶子Monad
Transformer类型格式就足以放心在for-comprehension中实行行令编制程序了。

这段日子大家能够用简短的言语来描述Monad存在的含义:它提供了风流浪漫套标准的方式来帮忙FP编制程序。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

相关文章