lovebetScalaz(10)- Monad:就是平种函数式编程模式-a design patternScalaz(43)- 总结 :FP就是实用的编程模式。

    Monad
typeclass不是平等栽类型,而是同样栽次设计模式(design
pattern),是泛函编程中最要的编程概念,因而众多行内人把FP又称作Monadic
Programming。这其间披露的Monad重要性则强烈。Scalaz是由此Monad
typeclass为数量运算的次序提供了一如既往效仿规范之编程方式,如周边的for-comprehension。而各异类型的Monad实例则会支持不同的次运算行为,如:Option
Monad在运算被只要遇到None值则会中途退出;State
Monad会确保状态值会伴随在程序运行流程直到了;List
Monad运算可能会见发出多独结果等等。Scalaz提供了累累见仁见智门类之Monad如:StateMonad,
IOMonad, ReaderMonad,
WriterMonad,MonadTransformer等等,这从旁一个角度也重温了Monad概念在泛函编程里的要紧。听起以上这些描述好像有点摸不着头脑,可能当把它在本篇最终总,不过我要么想念让大家来只要命之概念。对下面的讨论细节的掌握能有助。我们要打Monad
trait开始介绍吧:

  完成了针对Free
Monad这片情节的求学了解后,心头豁然开朗,存在心里对FP的多疑也一如既往扫而只是。之前也得在和大部分人同的无理概念,认为FP只抱学术性探讨、缺乏实际采用、运行效率低,很为难提高成为现实的软件开发模式。Free
Monad的起恰恰解决自身心里的疑云,更凑巧了自家本着FP的偏:Free
Monad提供了一样仿在Monad 算法内(在
for-comprehension内)的行令编程(imperative
programming)方法,解决了FP的繁杂语法,使Monadic编程更贴近传统编程模式的习惯及思,程序意图更易理解。Free
Monad的函数结构化(reification)有效化解了递归算法造成的堆栈溢起(stackoverflow)问题,使FP程序会平安运行,实现以切实中的下。

 1 trait Monad[F[_]] extends Applicative[F] with Bind[F] { self =>
 2   //// scalaz/Monad.scala
 3 
 4   override def map[A,B](fa: F[A])(f: A => B) = bind(fa)(a => point(f(a)))
 5 ...
 6 trait Applicative[F[_]] extends Apply[F] { self =>
 7   //// scalaz/Applicative.scala
 8   def point[A](a: => A): F[A]
 9 ...
10 trait Apply[F[_]] extends Functor[F] { self =>
11   //// scalaz/Apply.scala
12   def ap[A,B](fa: => F[A])(f: => F[A => B]): F[B]
13 ...
14 trait Bind[F[_]] extends Apply[F] { self =>
15   //// scalaz/Bind.scala
16 
17   /** Equivalent to `join(map(fa)(f))`. */
18   def bind[A, B](fa: F[A])(f: A => F[B]): F[B]
19 
20   override def ap[A, B](fa: => F[A])(f: => F[A => B]): F[B] = {
21     lazy val fa0 = fa
22     bind(f)(map(fa0))
23   }
24 ...

 
以攻scalaz初期,FP的花色和函数施用搞得自己深不得已,不适于:FP类型的Functor,Applicative,Monad等等为自家的印象是最为抽象的。而且接触到的关于这些项目的切实以例子又大多数是针对List,Option,Map这些教科书通用型的,感觉FP就是同栽对编程模式的学术探讨,是故来改变思维之,没什么实用价值。当然,FP的递归算法又重新强化了俺们本着实际中选用它的怀疑。但由Free
Monad反往回顾scalaz的这些基础项目以及函数,我接近渐渐地知道了它在scalaz这个FP工具库中留存的义。回到我了解scalaz的目的:就是冀征FP这种模式可变成平等种生育工具。之前已经了解了FP模式之优势而对它的骨子里利用或满怀来存疑。以自我粗浅的正统来讲,如果作为同种植实际可用之编程语言,起码得持有以下几点:

点这些类别trait的继承关系是这般的:Monad继承了Applicative和Bind,Applicative继承了Apply,
Apply继承了Functor,
Bind也继承了Apply。所以Monad同时以是Applicative和Functor,因为Monad实现了map和ap函数。一个Monad实例可以调用所有Applicative和Functor提供的零件函数。任何实例只需要实现抽象函数point和bind就好成为Monad实例,然后就可运用Monad所有的零件函数了。

1、语法简单,容易控制

Monad所提供的重要注入方法(injected
method)是于BindOps和MonadOps里。在BindOps里要提供了flatMap:
scalaz/syntax/BindSyntax.scala

2、表达式简洁、直白

 1 final class BindOps[F[_],A] private[syntax](val self: F[A])(implicit val F: Bind[F]) extends Ops[F[A]] {
 2   ////
 3   import Liskov.<~<, Leibniz.===
 4 
 5   def flatMap[B](f: A => F[B]) = F.bind(self)(f)
 6 
 7   def >>=[B](f: A => F[B]) = F.bind(self)(f)
 8 
 9   def ∗[B](f: A => F[B]) = F.bind(self)(f)
10 ...

3、能够确保运行安全

重中之重是是flatMap函数,在scalaz里用>>=来代表。这是一个豪门还至少耳熟的函数:好像flatMap就意味着了Monad。在MonadOps里提供的注入方法如下:scalaz/Syntax/MonadSyntax.scala

试想我们安会长期的编排fa.flatMap(a
=> fb.flatMap(b =>
fc.map(…)))这样的先后为?FP针对泛函结构F[A]的运算有着一样拟全新的数据结构和函数施用方式,没人能领略这样的主次表达的究竟是什么目的。这时我们遇到了flatMap函数的主意糖for-comprehension,它可用让咱们以一个for-loop里进行我们熟悉的行令式编程,就如下这样:

 1 final class MonadOps[F[_],A] private[syntax](val self: F[A])(implicit val F: Monad[F]) extends Ops[F[A]] {
 2   ////
 3 
 4   def liftM[G[_[_], _]](implicit G: MonadTrans[G]): G[F, A] = G.liftM(self)
 5 
 6   def whileM[G[_]](p: F[Boolean])(implicit G: MonadPlus[G]): F[G[A]] = F.whileM(p, self)
 7 
 8   def whileM_(p: F[Boolean]): F[Unit] = F.whileM_(p, self)
 9 
10   def untilM[G[_]](p: => F[Boolean])(implicit G: MonadPlus[G]): F[G[A]] = F.untilM(self, p)
11 
12   def untilM_(p: => F[Boolean]): F[Unit] = F.untilM_(self, p)
13 
14   def iterateWhile(p: A => Boolean): F[A] = F.iterateWhile(self)(p)
15 
16   def iterateUntil(p: A => Boolean): F[A] = F.iterateUntil(self)(p)
17 
18   ////
19 }
for {
 x <- getRecNo
 r <- getRecord(x)
 _ <- r.save() 
} yield ()

扣押起这些注入方法都是有些编程语言里的流水线控制语法(control
flow
syntax)。这是未是暗示着Monad最终见面实现某种编程语言?我们将这些函数的行使办法在后面的一对座谈去。我们事先来分析一下flatMap函数,因为就是只Monad代表函数。下面是Functor,Applicative和Monad施用函数格式比较:

除外for-yield后不就是是咱耳熟能详的编程方式吧?我们曾习惯并控制了这种编程方式。因为flatMap是Monad的运算函数,所以FP式的编程又于称作Monadic
Programming,直白来讲就是用Monad来编程,或者就是以一个Monad壳子(context)里编程。可以说scalaz的拥有东西最后还与Monad有关(everything
is about
Monad)。通过认证,任何Monad都不能不是Functor和Applicative,所以在scalaz里供的Functor,Applicative以及另的基础typeclass并无苟我们想象的那么好像没什么实用价值,实际上scalaz是经过这些基础typeclass为我们构建各种力量的Monad提供了支持的。现在总的来说这些基础typeclass还是值得询问之。而且看来要如拓展FP编程,就亟须先行控Monad应用,因为咱们用将所有东西还提升成Monad。那么Monad真的比如说许多人数深感的那样神秘、虚渺、触不可及为?答案是否定的。接触的基本上了咱们虽得了解Monad的显要意图就是是把一个算法,无论是一个值或者一个函数升格成Monad,这样我们就是可以当Monad-for-comprehension里应用它们了。看看scalaz里有的品类的Monad格式吧:

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

 

上述三种植函数款式基本上是相同的。大家都说马上就是是三栽FP的函数施用方式:在一个容器内开展函数的运算后将结果还养于容器内、得到的效益是如此的:F[A]
=>
F[B]。只是她各自就此不同的法子提供这动用的函数。Functor的map提供了寻常函数,Applicative通过容器提供了应用函数ap而Monad则是通过直接函数施用方式来兑现F[A]
=> F[B]:
直接对输入A进行函数施用并生一个F[B]结果。Monad的这种方法应不是严厉意义上之于容器内开展函数施用。从其它一个角度解析,Monad可以让视作某种算法(computation)。Monad
F[A]表示了针对一个A类型数据的算法(computation)。如果这么说那么Monad就起矣全新的分解:Monad就是同样种植可以对某种类型的数据值进行连接计算的算法(computation):如果我们将flatMap串联起来的言辞就会见是如此的:

case class State (run: S => (A,S))
case class Reader(run: A => B)
case class Writer(run: (W, A))
...
1 //   fa.flatMap(a => fb.flatMap(b => fc.flatMap(c => fd.map(...))))

 

在这里fa,fb,fc都是F[T]这么的算法。可以观看当我们将flatMap串接起后便形成了一个串型(sequencial)流程(workflow)的F[]算法。为了还懂的垂询串接flatMap的义,我们所以同一的for-comprehension来演示:

其还是拿常备的函数或者运算包嵌在一个结构里然后在贯彻这个类型的flatMap函数时体现这些运算的切实可行意思。这些道理在scalaz的源代码里还好抱印证。所以我们平素未需畏惧Monad,应该下积极态度去尽量了解掌握其。我记忆中于费心的凡Monad转换和力量结合,它们都关涉到花色匹配,需要比充分的设想空间。

1 //   for {
2 //      a <- (fa: F[A])
3 //      b <- (fb: F[A])
4 //      c <- (fc: F[A])
5 //   } yield { ... }

好了,有矣Monad和各种功能转移、集合方式,我们好以for-comprehension里展开熟悉的编程了。那么会无会见产出于一个for-loop里冒出几百履指令的景况吧?我以为未会见,因为我们好用函数组合方式将一个深程序分解变成各种功能单一的简短函数,然后逐层进行重组,最终的主次太多为就算是十几二十行。这种组合特性有赖于Free
Monad提供的算式/算法关注分离(program/interpret separation of
concern)模式。它可把影响函数组合的副作用放到算法(interpret)阶段,让咱们会在算式中贯彻程序中的构成。这个我用以下的代码来演示一下:

这么表达会越清楚了:我们先运算fa,得到结果a后随着运算fb,得出结果b后再运算fc,得出结果c
… 这像是相同段落行令程序(imperative
program)。我们更就此个形象点的例子来演示说明:

val prgGetData = for {
  x <- getRecNo
  r <- getRecord(x)
} yield r

val prgUpdateRecord = for {
  x <- getData
  r <- prgGetData
  - <- r.updateAndSave()
} yield ()

prgUpdateRecord.run(dbActions)

 

双重起一个问题不怕是FP的运算方式了:我们可以看出运算一连串的flatMap是如出一辙种植递归算法,除非动用尾递归算法,compiler是心有余而力不足对算法进行优化的,那么运算flatMap就充分爱会有堆栈溢出荒唐(stackoverflow
error),无法保全程序运行安全。Free
Monad是经函数结构化,既是把flatMap函数作为同样种植多少存放于heap内存上,然后经折叠算法逐个运算,这和习俗的函数引用方式:即经过储藏室设置运算环境来从不同,Free
Monad是为此heap换stack,避免了递归算法容易出现的库溢起问题。这方面又解决了FP程序运行安全问题。

 1 class Foo { def bar: Option[Bar] }
 2 class Bar { def baz: Option[Baz] }
 3 class Bar { def baz: Option[Baz] }
 4 
 5 def compute(maybeFoo: Option[Foo]): Option[Int] =
 6  maybeFoo.flatMap { foo =>
 7   foo.bar.flatMap { bar =>
 8     bar.baz.map { baz =>
 9       baz.compute
10     }
11   }
12  }
13 def compute2(maybeFoo: Option[Foo]): Option[Int] =
14   for {
15       foo <- maybeFoo
16       bar <- foo.bar
17       baz <- bar.baz
18   }  yield baz.compute

通过调研、演练后为主控制了Monadic
Programming(MP)的法子艺术。现在拿它们总如下:

 

MP编程可分割三只环节:

得看到,每一个算法都靠前面算法得出的结果。从夫例子我们可汲取Monad的串型运算(sequencial
computation)特性。确切来说,flatMap并无合乎并行运算,所以我们得Applicative。这是盖Applicative是在既有的容器中运算,而flatMap则会重新创设新的器皿(在Monad的社会风气里容器即为算法(computation)。但是坐咱们提了Monad就是Applicative,所以Monad也堪实现互动运算。Applicative
的 ap 函数可以就此 flatMap实现:

1、编写程序功能描述,是一样错代数语法(AST)。不是当时程序(Programm)

1 // ap[A,B](ma: F[A])(mf: F[A => B]): F[B] = mf.flatMap(f => ma.flatMap(a => point(f(a)))  

2、把职能描述对承诺交实际的机能实现方式

也得以就此flatMap来实现Functor的map函数:

3、最后,运算选定的兑现方式

 

分成具体的手续如下:

1 // map[A,B](fa: F[A])(f: A => B): F[B] = fa.flatMap(a => point(f(a)))  

1、ADT:模拟语句,用F[A]仿佛数据类型来模拟指令

 

 

由点的事例好像得领悟一些有关FP即Monadic
Programming的说教。形象的来讲:这个所谓的算法Monad
F[]不怕接近是当F[]这样个盖子里展开传统编程:还笔记着吧,FP编程既是纯函数(pure
function)对F[T]里的T值进行演算,没有中间变量(temp
variable),没有副作用(no
side-effect)。但本发矣Monad,我们虽可采用传统的行令编程(imperative
programming)了。再像一点吧上面的for loop就比如F[]盖子,在for
loop内可以开展说明变量,更新状态相当OOP式行令编程。但这些生成(mutability)不会见漏出for
loop之外。不过,本篇所陈述Monad编程的单一局限性还是异常肯定的:因为当for
loop 内部的操作函数都得回到同一种植档次的Monad实例如:Option[],
List[],SomeType[]等等。而且程序运算行为仅会叫相同种植档次的性状所主宰。如上面所描述,Monad实例的花色控制Monadic程序的运算行为。每一样种植Monad实例的程序可以来两样的运算方式。如果欲多种类型行为之Monad程序,就得采取Monad
Transformer
typeclass了。这个在将来之座谈着自会提及,现在看似说的过分了。我们或回Monad的基本操作。

object FreeADTs {
  trait Dialog[A]
  case class Ask(prompt: String) extends Dialog[String]
  case class Tell(msg: String) extends Dialog[Unit]
  implicit def dialogToFree[A](da: Dialog[A]) = Free.liftF(da)
}

Option是scala标准库的一个种。它就是只Monad,所以可以下flatMap:

 

1 2.some flatMap {x => (x + 3).some }               //> res0: Option[Int] = Some(5)
2 2.some >>= { x => (x + 3).some }                  //> res1: Option[Int] = Some(5)
3 (none: Option[Int]) >>= {x => (x + 3).some }      //> res2: Option[Int] = None

2、lift:把F[A]升格成Free
Monad

我们得以就此Monad[T]
point来把一个习以为常值A升格到T[A]:

 

 

  implicit def dialogToFree[A](da: Dialog[A]) = Free.liftF(da)
1 Monad[Option].point(2)                            //> res3: Option[Int] = Some(2)
2 Monad[Option].point(2) >>= {x => Monad[Option].point(x + 3)}
3                                                   //> res4: Option[Int] = Some(5)
4 (None: Option[Int]) >>= {x => Monad[Option].point(x + 3)}
5                                                   //> res5: Option[Int] = None

 

 

3、AST:代数程序,描述程序功能

于面的例子里我们不停提及Option
Monad是发原因的,因为Option类型的Monad典型实例,在决定运算流程时最有风味:可以在半路退出,在遇到None值时好及时终止运算。

 

俺们用一个比现实点的事例来演示:我刚好尝试用自己之计来练习举重

自己最好多克举起50KG、每个杠铃片重2.5公斤、杠铃两端不必平衡,但一边不得超过其它一面多于3单杠铃片(多3只还无问题)。试着用一个自定义类型来套举重:

1 type Discs = Int  //杠铃片数量
2 case class Barbell(left: Discs, right: Discs) {
3     def loadLeft(n: Discs): Barbell = copy(left = left + n)
4     def loadRight(n: Discs): Barbell = copy(right = right + n)
5 }
6 Barbell(0,0).loadLeft(1)                          //> res8: Exercises.monad.Barbell = Barbell(1,0)
7 Barbell(1,0).loadRight(1)                         //> res9: Exercises.monad.Barbell = Barbell(1,1)
8 Barbell(2,1).loadLeft(-1)                         //> res10: Exercises.monad.Barbell = Barbell(1,1)

今天这个自定义类型Barbell是得跟当前杠铃左右分量状态的。现在本身管为杠铃上多重量片的长河串联起:

1 Barbell(0,0).loadLeft(1).loadRight(2).loadRight(100).loadLeft(2).loadRight(-99)
2                                                   //> res11: Exercises.monad.Barbell = Barbell(3,3)

 

好望此过程被略环节就超越了自身之力,但杠铃最终状态好像还是客观之。我们要以重配置不客观的时就及时停下。现在咱们得就此Option来落实这项功能:

 1 type Discs = Int  //杠铃片数量
 2 case class Barbell(left: Discs, right: Discs) {
 3   def loadLeft(n: Discs): Option[Barbell] = copy(left = left + n) match {
 4     case Barbell(left,right) => if ( (left+right <= 20) && math.abs(left-right) <=3 ) Some(Barbell(left,right)) else None
 5     case _ => None
 6   }
 7   def loadRight(n: Discs): Option[Barbell] = copy(right = right + n) match {
 8     case Barbell(left,right) => if ( (left+right <= 20) && math.abs(left-right) <=3 ) Some(Barbell(left,right)) else None
 9     case _ => None
10   }
11 }
12 Barbell(0,0).loadLeft(1)                          //> res8: Option[Exercises.monad.Barbell] = Some(Barbell(1,0))
13 Barbell(1,0).loadRight(1)                         //> res9: Option[Exercises.monad.Barbell] = Some(Barbell(1,1))
14 Barbell(2,1).loadLeft(-1)                         //> res10: Option[Exercises.monad.Barbell] = Some(Barbell(1,1))
15 Barbell(0,0).loadLeft(4)                          //> res11: Option[Exercises.monad.Barbell] = None
16 Barbell(15,1).loadRight(15)                       //> res12: Option[Exercises.monad.Barbell] = None

超越重量平衡的景返回了None。现在返回值是个Option,而Option是单Monad,所以我们好据此flatMap把每个环节串联起来:

1 Barbell(0,0).loadLeft(3) >>= {_.loadRight(3)}     //> res13: Option[Exercises.monad.Barbell] = Some(Barbell(3,3))
2 Barbell(0,0).loadLeft(3) >>= {_.loadRight(3) >>= {_.loadRight(1)}}
3                                                   //> res14: Option[Exercises.monad.Barbell] = Some(Barbell(3,4))
4 Barbell(0,0).loadLeft(3) >>= {_.loadRight(3) >>= {_.loadRight(1) >>= {_.loadLeft(4)}}}
5                                                   //> res15: Option[Exercises.monad.Barbell] = Some(Barbell(7,4))
6 Barbell(0,0).loadLeft(1) >>= {_.loadRight(5) >>= {_.loadLeft(2)}}
7                                                   //> res16: Option[Exercises.monad.Barbell] = None
8 Monad[Option].point(Barbell(0,0)) >>= {_.loadLeft(3) >>= {_.loadRight(6)}}
9                                                   //> res17: Option[Exercises.monad.Barbell] = Some(Barbell(3,6))

我们的尾声目的是因此for-comprehension来抒发,会愈清楚:

 

 1 def addWeight: Option[Barbell] = for {
 2     b0 <- Monad[Option].point(Barbell(0,0))
 3     b1 <- b0.loadLeft(3)
 4     b2 <- b1.loadRight(3)
 5 } yield b2                                        //> addWeight: => Option[Exercises.monad.Barbell]
 6 addWeight                                         //> res18: Option[Exercises.monad.Barbell] = Some(Barbell(3,3))
 7 
 8 def addWeight1: Option[Barbell] = for {
 9     b0 <- Monad[Option].point(Barbell(0,0))
10     b1 <- b0.loadLeft(4)
11     b2 <- b1.loadRight(3)
12 } yield b2                                        //> addWeight1: => Option[Exercises.monad.Barbell]
13 addWeight1                                        //> res19: Option[Exercises.monad.Barbell] = None

 

于上述之例证可以汲取:实现了一个数据类型的Monad实例后即便足以拿走以这项目控制运算行为的相同栽简易的编程语言,这种编程语言可以在for
loop内部贯彻传统的行令编程风格。

当本篇讨论中我们介绍了Monad实际上是同等种植编程模式,并且示范了大概的for
loop内部流程运算。在脚的平等名目繁多讨论中我们以会见了解又多花色的Monad,以及Monad如何会成功能完善的编程语言。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

object FreeASTs {
  import FreeADTs._
  val prg: Free[Dialog,Unit] = for {
    x <- Ask("What's your first name?")
    _ <- Tell(s"Hi, $x")
    y <- Ask("What's your last name?")
    _ <- Tell(s"Hello $x $y!!!")
  } yield()
}

 

4、Interpret:把F[A]对应到G[A]上。G[A]凡兑现具体效果的Monad。以下供了片栽不同作用的贯彻方式

object FreeInterp {
    import FreeADTs._
    object DialogConsole extends (Dialog ~> Id) {
      def apply[A](da: Dialog[A]): Id[A] = da match {
        case Ask(p) => println(p); readLine
        case Tell(s) => println(s)

      }
    }
    type WF[A] = Map[String,String] => A
    type Tester[A] = WriterT[WF,List[String],A]
    implicit val testerMonad = WriterT.writerTMonad[WF,List[String]]
    def testerToWriter[A](f: Map[String,String] => (List[String],A)) = WriterT[WF,List[String],A](f)
    object DialogTester extends (Dialog ~> Tester) {
      def apply[A](da: Dialog[A]): Tester[A] = da match {
        case Ask(p) => testerToWriter {m => (List(),m(p))}
        case Tell(s) => testerToWriter {m => (List(s),())}
      }
    }
}

5、Run:最后,对促成方式展开演算

object SimpleFree extends App {
 import FreeASTs._
 import FreeInterp._

 //prg.foldMapRec(DialogConsole)
  prg.foldMapRec(DialogTester).run(
    Map("What's your first name?" -> "Johnny", "What's your last name?" -> "Foo")
  )._1.map(println)

}

要出现强模拟语法的情,我们好就此inject方式把各种语法注入Coproduct,形成一个多语法的言语集合。具体的语法集合以及多语法的效力落实对应运算可以参见眼前这首博客中之座谈。

 

 

 

 

 

 

 

 

相关文章