正是壹种函数式编制程序格局

    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等等,那从另3个角度也再3了Monad概念在泛函编程里的重点。听起来以上这个描述好像有个别摸不着头脑,恐怕应该把它们位于本篇最终计算,可是小编依旧想让大家有个大的定义。对上边包车型客车座谈细节的理解能具备扶助。我们如故从Monad
trait开始介绍吧:

Monad
typeclass不是1种档次,而是一种程序设计方式(design
pattern),是泛函编制程序中最要紧的编制程序概念,因此众多行老婆把FP又称作Monadic
Programming。那个中揭露的Monad首要性则分明。Scalaz是因而Monad
typeclass为数据运算的次序提供了一套规范的编制程序格局,如周边的for-comprehension。而各异门类的Monad实例则会帮忙差异的程序运算行为,如:Option
Monad在运算中如果赶上None值则会中途退出;State
Monad会确定保证状态值会伴随着程序运营流程直到终结;List
Monad运算恐怕会生出多少个结果等等。Scalaz提供了很多不1类型的Monad如:StateMonad,
IOMonad, ReaderMonad,
WriterMonad,MonadTransformer等等,那从另一个角度也一再了Monad概念在泛函编制程序里的根本。听起来以上那些描述好像有点摸不着头脑,恐怕应该把它们放在本篇最后总计,可是本人大概想让大家有个大的定义。对上边包车型客车斟酌细节的明白能享有帮助。大家依旧从Monad
trait开端介绍吧:

 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 ...
 1 trait Monad[F[_]] extends Applicative[F] with Bind[F] { self => 2   //// scalaz/Monad.scala 3  4   override def map[A,B](f: A => B) = bind(a => point 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.scala12   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.scala16 17   /** Equivalent to `join`. */18   def bind[A, B](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 = fa22     bind23   }24 ...

地点这几个项目trait的接二连三关系是这么的:Monad继承了Applicative和Bind,Applicative继承了Apply,
Apply继承了Functor,
Bind也继续了Apply。所以Monad同时又是Applicative和Functor,因为Monad达成了map和ap函数。1个Monad实例可以调用全部Applicative和Functor提供的组件函数。任何实例只要求贯彻抽象函数point和bind就足以改为Monad实例,然后就能够运用Monad全部的零件函数了。

下面那么些项目trait的存在延续关系是这么的:Monad继承了Applicative和Bind,Applicative继承了Apply,
Apply继承了Functor,
Bind也继续了Apply。所以Monad同时又是Applicative和Functor,因为Monad完结了map和ap函数。2个Monad实例能够调用全数Applicative和Functor提供的零件函数。任何实例只须求达成抽象函数point和bind就能够改为Monad实例,然后就足以采取Monad全数的机件函数了。

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

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

 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 ...
 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 6  7   def >>=[B](f: A => F[B]) = F.bind 8  9   def ∗[B](f: A => F[B]) = F.bind10 ...

要害是其一flatMap函数,在scalaz里用>>=来代表。那是二个大家都至少耳熟的函数:好像flatMap就意味着了Monad。在MonadOps里提供的注入方法如下:scalaz/Syntax/MonadSyntax.scala

最首借使这么些flatMap函数,在scalaz里用>>=来代表。那是二个大家都至少耳熟的函数:好像flatMap就意味着了Monad。在MonadOps里提供的注入方法如下:scalaz/Syntax/MonadSyntax.scala

 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 }
 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 5  6   def whileM[G[_]](p: F[Boolean])(implicit G: MonadPlus[G]): F[G[A]] = F.whileM 7  8   def whileM_(p: F[Boolean]): F[Unit] = F.whileM_ 9 10   def untilM[G[_]](p: => F[Boolean])(implicit G: MonadPlus[G]): F[G[A]] = F.untilM11 12   def untilM_(p: => F[Boolean]): F[Unit] = F.untilM_13 14   def iterateWhile(p: A => Boolean): F[A] = F.iterateWhile15 16   def iterateUntil(p: A => Boolean): F[A] = F.iterateUntil17 18   ////19 }

看起来这么些注入方法都以一对编制程序语言里的流水线控制语法(control
flow
syntax)。那是否暗示着Monad最终会促成某种编制程序语言?大家把那一个函数的使用办法放在前边的有些谈论去。大家先来分析一下flatMap函数,因为那是个Monad代表函数。下边是Functor,Applicative和Monad施用函数格式相比较:

看起来这么些注入方法都以一些编程语言里的流程序控制制语法(control
flow
syntax)。那是否暗示着Monad最后会兑现某种编制程序语言?大家把那些函数的行使方式放在前边的1对座谈去。大家先来分析一下flatMap函数,因为那是个Monad代表函数。上面是Functor,Applicative和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]
1 // Functor    :  map[A,B]    (f:   A => B):  F[B]2 // Applicative:  ap[A,B]     (f: F[A => B]): F[B] 3 // Monad      :  flatMap[A,B](f: A => F[B]): F[B]

如上二种函数款式基本上是如出1辙的。大家都说那就是两种FP的函数施用格局:在叁个器皿内举行函数的演算后把结果还留在容器内、得到的效能是如此的:F[A]
=>
F[B]。只是它们分别用差异的主意提供这一个利用的函数。Functor的map提供了通常函数,Applicative通过容器提供了应用函数ap而Monad则是经过一贯函数施用情势来兑现F[A]
=> F[B]:
直接对输入A进行函数施用并爆发1个F[B]结果。Monad的那种艺术应该不是严谨意义上的在容器内开始展览函数施用。从另3个角度解析,Monad能够被看作某种算法(computation)。Monad
F[A]代表了对一个A类型数据的算法(computation)。假设如此说那么Monad就有了全新的表明:Monad就是壹种能够对某体系型的数据值进行延续总结的算法(computation):假使大家把flatMap串联起来的话就会是如此的:

上述二种函数款式基本上是均等的。大家都说那就是两种FP的函数施用格局:在1个容器内开始展览函数的运算后把结果还留在容器内、获得的功力是那般的:F[A]
=>
F[B]。只是它们各自用不相同的艺术提供那一个动用的函数。Functor的map提供了一般函数,Applicative通过容器提供了利用函数ap而Monad则是透过直接函数施用格局来落到实处F[A]
=> F[B]:
直接对输入A举行函数施用并发出1个F[B]结果。Monad的那种方法应该不是严苛意义上的在容器内开展函数施用。从另一个角度分析,Monad能够被当做某种算法(computation)。Monad
F[A]代表了对二个A类型数据的算法(computation)。如若如此说那么Monad就有了全新的诠释:Monad就是1种能够对某体系型的数据值进行三番五次总结的算法(computation):倘使我们把flatMap串联起来的话就会是如此的:

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

在这里fa,fb,fc都是F[T]那般的算法。可以观看当我们把flatMap串接起来后就形成了三个串型(sequencial)流程(workflow)的F[]算法。为了更明了的问询串接flatMap的含义,大家用同样的for-comprehension来演示:

在这里fa,fb,fc都是F[T]如此的算法。可以看到当我们把flatMap串接起来后就形成了二个串型(sequencial)流程的F[]算法。为了更明白的询问串接flatMap的含义,大家用同壹的for-comprehension来演示:

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

这么说明会越来越清晰了:大家先运算fa,获得结果a后接着运算fb,得出结果b后再运算fc,得出结果c
… 这像是1段行令程序(imperative
program)。咱们再用个形象点的例证来演示表达:

诸如此类表明会越来越清楚了:我们先运算fa,获得结果a后继之运算fb,得出结果b后再运算fc,得出结果c
… 那像是1段行令程序(imperative
program)。大家再用个形象点的例证来演示表达:

 

 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.compute10     }11   }12  }13 def compute2(maybeFoo: Option[Foo]): Option[Int] =14   for {15       foo <- maybeFoo16       bar <- foo.bar17       baz <- bar.baz18   }  yield baz.compute
 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

能够见见,每三个算法都依赖前面算法得出的结果。从这些事例大家能够得出Monad的串型运算(sequencial
computation)本性。确切来说,flatMap并不吻合并行运算,所以我们供给Applicative。这是因为Applicative是在既有的容器中运算,而flatMap则会重新创制新的器皿(在Monad的世界里容器即为算法(computation)。不过因为我们讲过Monad就是Applicative,所以Monad也能够兑现互相之间运算。Applicative
的 ap 函数能够用 flatMap达成:

 

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

能够看到,每3个算法都信赖前边算法得出的结果。从那几个例子大家能够得出Monad的串型运算(sequencial
computation)天性。确切来说,flatMap并不吻合并行运算,所以大家供给Applicative。那是因为Applicative是在既有的容器中运算,而flatMap则会重新创造新的器皿(在Monad的世界里容器即为算法(computation)。可是因为我们讲过Monad就是Applicative,所以Monad也能够兑现相互之间运算。Applicative
的 ap 函数能够用 flatMap完成:

也能够用flatMap来达成Functor的map函数:

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

也足以用flatMap来实现Functor的map函数:

从地点的事例好像能够领会1些关于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的基本操作。

 

Option是scala标准库的三个类型。它已经是个Monad,所以能够运用flatMap:

1 // map[A,B](fa: F[A])(f: A => B): F[B] = fa.flatMap(a => point(f(a)))  
1 2.some flatMap {x => (x + 3).some }               //> res0: Option[Int] = Some2 2.some >>= { x => (x + 3).some }                  //> res1: Option[Int] = Some3 (none: Option[Int]) >>= {x => (x + 3).some }      //> res2: Option[Int] = None

 

我们能够用Monad[T]
point来把二个日常值A升格到T[A]:

从地点的例证好像能够通晓1些关于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的基本操作。

1 Monad[Option].point(2)                            //> res3: Option[Int] = Some2 Monad[Option].point(2) >>= {x => Monad[Option].point(x + 3)}3                                                   //> res4: Option[Int] = Some4 (None: Option[Int]) >>= {x => Monad[Option].point(x + 3)}5                                                   //> res5: Option[Int] = None

Option是scala标准库的三个系列。它早已是个Monad,所以能够应用flatMap:

在上面的例子里大家不停聊到Option
Monad是有缘由的,因为Option类型的Monad典型实例,在控制运算流程时最有特点:能够在中途退出,在遭逢None值时得以立时终止运算。

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

大家用三个比较现实点的例证来演示:笔者正尝试用自个儿的诀窍来演练举重

本身最多能举起50KG、各类杠铃片重二.5公斤、杠铃两端不必平衡,但另壹方面不得超越另2头多于3个杠铃片。试着用多少个自定义类型来效仿举重:

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

今天那么些自定义类型Barbell是足以跟踪当前杠铃左右份量状态的。未来本人把往杠铃上加码重量片的进度串联起来:

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

可以看看那几个进程中多少环节已经超(英文名:jīng chāo)出了本人的能力,但杠铃最终状态好像照旧客观的。大家须求在重量配置不客观的时候就立马终止。未来大家得以用Option来完结那项功用:

 1 type Discs = Int  //杠铃片数量 2 case class Barbell(left: Discs, right: Discs) { 3   def loadLeft: 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: 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 _ => None10   }11 }12 Barbell(0,0).loadLeft(1)                          //> res8: Option[Exercises.monad.Barbell] = Some(Barbell13 Barbell(1,0).loadRight(1)                         //> res9: Option[Exercises.monad.Barbell] = Some(Barbell14 Barbell(2,1).loadLeft(-1)                         //> res10: Option[Exercises.monad.Barbell] = Some(Barbell15 Barbell(0,0).loadLeft(4)                          //> res11: Option[Exercises.monad.Barbell] = None16 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(Barbell2 Barbell(0,0).loadLeft(3) >>= {_.loadRight(3) >>= {_.loadRight(1)}}3                                                   //> res14: Option[Exercises.monad.Barbell] = Some(Barbell4 Barbell(0,0).loadLeft(3) >>= {_.loadRight(3) >>= {_.loadRight(1) >>= {_.loadLeft(4)}}}5                                                   //> res15: Option[Exercises.monad.Barbell] = Some(Barbell6 Barbell(0,0).loadLeft(1) >>= {_.loadRight(5) >>= {_.loadLeft(2)}}7                                                   //> res16: Option[Exercises.monad.Barbell] = None8 Monad[Option].point(Barbell(0,0)) >>= {_.loadLeft(3) >>= {_.loadRight(6)}}9                                                   //> res17: Option[Exercises.monad.Barbell] = Some(Barbell

我们的最终目标是用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 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怎么样能变成效用完善的编程语言。

我们可以用Monad[T]
point来把一个普通值A升格到T[A]:

 

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

 

在地点的例证里大家不住聊到Option
Monad是有缘由的,因为Option类型的Monad典型实例,在决定运算流程时最有风味:能够在中途退出,在遇见None值时能够登时截止运算。

咱俩用四个相比现实点的例子来演示:笔者正尝试用自个儿的措施来练习举重

自己最多能举起50KG、每一个杠铃片重二.伍公斤、杠铃两端不必平衡,但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实例后就足以拿走以那些类型控制运算行为的1种简单的编制程序语言,那种编程语言能够在for
loop内部贯彻守旧的行令编制程序风格。

在本篇研讨中大家介绍了Monad实际上是一种编制程序方式,并且示范了简约的for
loop内部流程运算。在底下的1种类斟酌中大家将会询问更几类其余Monad,以及Monad怎么样能变成功效完善的编制程序语言。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

相关文章