转载

【函数式】Monads模式初探——for解析式

for表达式是monad语法糖

先看一组示例:

caseclassPerson(name: String, isMale: Boolean, children: Person*)  vallara = Person("Lara",false) valbob = Person("Bob",true) valjulie = Person("Julie",false, lara, bob) valpersons = List(lara, bob, julie)  println(  persons filter (p => !p.isMale) flatMap (p =>  (p.children map (c => (p.name, c.name)))) )  println( for(p <- persons;if!p.isMale; c <- p.children) yield(p.name, c.name) ) // output is // List((Julie,Lara), (Julie,Bob)) 

Person类包含了人员名称,是否是男性,以及他的孩子的字段。代码的意义是找出列表中所有的妈妈和孩子结对的名称。分别使用了map、flatMap、filter的方式进行查询,还使用了for表达式完成,得到相同的结果。

实际上, Scala编译器能够把所有使用yield产生结果的for表达式转移为高阶方法map、flatMap及filter的组合调用 。所有的不带yield的for循环都会被转移为仅对filter和佛reach的调用。

for表达式说明

for表达式形式如下:

for (seq) yield expr

这里,seq由生成器、定义及过滤器组成序列,以分号隔开。如果在for表达式中用花括号代替小括号包围表达式序列,那么分号是可选的。

比如下面的示例:

for(p <- persons; n = p.name;if(n startsWith"To")) yieldn  for{  p <- persons //生成器  n = p.name //定义 if(n startsWith"To")//过滤器 } yieldn 

生成器的形式为 patten <- expression ,表达式expression典型的返回值是列表,不过它可以泛化。模式pattern一一匹配列表里的所有元素。如果匹配成功,模式中的变量将绑定元素的相应成分。但即使匹配失败也不会抛出MatchError,而只是在迭代中丢弃这个元素罢了。

所有的for表达式都以生成器开始。如果for表达式中有若干生成器,那么后面的生成器比前面的变化的更快。

for表达式的转译

对于每一个Monad来说,都支持for表达式,而每个for表达式都可以用三个高阶函数map、flatMap及filter表达。

基本的转译方式

  • 带一个生成器的for表达式
    for (x <- expr1) yield expr2 转译为 expr1.map(x => expr2)
  • 以生成器和过滤器开始的for表达式
    for (x <- expr1 if expr2) yield expr3
    第一个表达式可以转译成 for (x <- expr1 filter (x => expr2)) yield expr3
  • 以两个生成器开始的for表达式
    for (x <- expr1; y <- expr2; seq) yield expr3
    假设seq是任意序列的生成器、定义及过滤器,也可能为空。两个生成器被转译为flatMap的应用:
    expr1.flatMap(x => for (y <- expr2; seq) yield expr3 )
    这就生成了另一个传递给flatMap的函数值形式的for表达式。

再举个例子:

// 第一步转译 for(n <- ns;  o <- os;  p <- ps) yieldn*o*p // 第二步转译 ns flatMap {n => for(o <- os;  p <- ps) yieldn*o*p} // 第三步转译 ns flatMap { n =>  os flatMap { o => for(p <- ps) yieldn*o*p}} // 第四步转译 ns flatMap {n =>  os flatMap {o =>  {ps map {p => n*o*p}}}} 

转译for循环

for表达式也有一个命令式(imperative)的版本,用于那些你只调用一个函数,不返回任何值而仅仅执行了副作用,这个版本去掉了yield声明。

for循环的转译版本只需用到foreach, for (x <- expr1) body ,转译为 expr1 foreach (x => body)

更大的例子是, for (x <- expr1; if expr2; y <- expr3) body 。它将被转译为:

expr1 filter (x => expr2) foreach(x =>  expr3 foreach(y => body)) 

foreach依然可以使用map来实现:

class M[A] {  def map[B](f: A => B): M[B] = ...  def flatMap[B](f: A => M[B]): M[B] = ...  def foreach[B](f: A => B): Unit = {  map(f)  ()  } } 

foreach可以通过调用map并丢掉结果来实现。不过这么做运行效率不高,所以scala允许你用自己的方式定义foreach。

转译定义

如果for表达式中内嵌定义,如 for (x <- expr1; y = expr2; seq) yield expr3

那么将转译为 for ((x, y) <- for (x <- expr1) yield (x, expr2); seq) yield expr3

这里每次产生新的x值的时候,expr2都被重新计算。所以这可能会浪费计算资源,造成重复计算。

比如下面的例子和更好的写法:

for(x <-1to100; y = expensiveComputationNotInvolvingX) yieldx*y  // better code valy = expensiveComputationNotInvolvingX for(x <-1to1000)yieldx*y 

生成器中的模式

如果生成器的左侧是模式pat而不是简单变量,那么转译方法将变得复杂很多。

绑定变量元组

for ((x1, ..., xn) <- expr1) yield expr2

转译为:

expr1.map {case (x1, ..., xn) => expr2}

任意模式

for (pat <- expr1) yield expr2

转译为:

expr1 filter { casepat =>true case_ =>false } map { casepat => expr2 } 

即,生成的条目首先经过过滤并且仅有那些匹配与pat的才会被映射。因此,这保证了模式匹配生成器不会抛出MatchError。

小结

因为for表达式的转译仅依赖于map、flatMap和filter的搭配,所以可以吧for表达式应用于大批数据类型(这些数据类型可以用Monad来描述和概括)上。

除了列表、数组之外,Scala标准库中还有许多其他类型支持四种方法(map、flatMap、filter、foreach),从而允许for表达式存在。同样,如果你自己的数据类型定义了需要的方法也可以完美支持for表达式。如果只定义map、flatMap、filter、foreach这些方法的子集,从而部分支持for表达式或循环。

规则如下:

  • 如果定义了map,可以允许单一生成器组成的for表达式
  • 如果定义了flatMap和map,可以允许若干个生成器组成的for表达式
  • 如果定义了foreach,允许for循环
  • 如果定义了filter,for表达式中允许以if开头的过滤器表达式

for表达式的转译发生在类型检查之前。这可以保持最大的灵活性,因为接下来只需for表达式展开的结果通过类型检查即可。

在函数式编程中,Monad定制了map、flatMap和filter功能,它可以解释多种类型的计算,包括从集合、状态和I/O操纵的计算、回溯计算以及交易等,不一而足。

转载请注明作者Jason Ding及其出处

jasonding.top

Github博客主页(http://blog.jasonding.top/)

CSDN博客(http://blog.csdn.net/jasonding1354)

简书主页(http://www.jianshu.com/users/2bd9b48f6ea8/latest_articles)

Google搜索jasonding1354进入我的博客主页

原文  http://blog.jasonding.top/2016/03/05/Functional Programming/【函数式】Monads模式初探——for解析式/
正文到此结束
Loading...