设为首页 加入收藏

TOP

Scalaz(10)- Monad:就是一种函数式编程模式-a design pattern(二)
2017-10-10 12:13:36 】 浏览:4677
Tags:Scalaz Monad 就是 函数 编程 模式 design pattern
是通过直接函数施用方式来实现F[A] => F[B]: 直接对输入A进行函数施用并产生一个F[B]结果。Monad的这种方式应该不是严格意义上的在容器内进行函数施用。从另一个角度分析,Monad可以被视作某种算法(computation)。Monad F[A]代表了对一个A类型数据的算法(computation)。如果这样说那么Monad就有了全新的解释:Monad就是一种可以对某种类型的数据值进行连续计算的算法(computation):如果我们把flatMap串联起来的话就会是这样的:

1 // fa.flatMap(a => fb.flatMap(b => fc.flatMap(c => fd.map(...))))
在这里fa,fb,fc都是F[T]这样的算法。可以看出当我们把flatMap串接起来后就形成了一个串型(sequencial)流程(workflow)的F[]算法。为了更清楚的了解串接flatMap的意义,我们用同等的for-comprehension来示范:
1 // for { 2 // a <- (fa: F[A]) 3 // b <- (fb: F[A]) 4 // c <- (fc: F[A]) 5 // } yield { ... }
这样表达会更加清晰了:我们先运算fa,得到结果a后接着运算fb,得出结果b后再运算fc,得出结果c ... 这像是一段行令程序(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.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](ma: F[A])(mf: F[A => B]): F[B] = mf.flatMap(f => ma.flatMap(a => point(f(a)))  
也可以用flatMap来实现Functor的map函数:

 

1 // map[A,B](fa: F[A])(f: A => B): F[B] = fa.flatMap(a => point(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的基本操作。

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

我们可以用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、每个杠铃片重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
首页 上一页 1 2 3 下一页 尾页 2/3/3
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇Scalaz(9)- typeclass:checki.. 下一篇Scalaz(11)- Monad:你存在的..

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目