在上一节我们介绍了Monad。我们知道Monad是一个高度概括的抽象模型。好像创造Monad的目的是为了抽取各种数据类型的共性组件函数汇集成一套组件库从而避免重复编码。这些能对什么是Monad提供一个明确的答案吗?我们先从上节设计的Monad组件库中的一些基本函数来加深一点对Monad的了解:

   trait Monad[M[_]] extends Functor[M] {
def unit[A](a: A): M[A]
def flatMap[A,B](ma: M[A])(f: A => M[B]): M[B]
def map[A,B](ma: M[A])(f: A => B): M[B] = {
flatMap(ma){a => unit(f(a))}
}
def map2[A,B,C](ma: M[A], mb: M[B])(f: (A,B) => C): M[C] = {
flatMap(ma) { a => map(mb){ b => f(a,b) }}
}
def sequence[A](lm: List[M[A]]): M[List[A]] = {
lm.foldRight(unit(Nil: List[A])){(a,b) => map2(a,b){_ :: _} }
}
//递归方式sequence
def sequence_r[A](lm: List[M[A]]): M[List[A]] = {
lm match {
case Nil => unit(Nil: List[A])
case h::t => map2(h,sequence_r(t)){_ :: _}
}
}
//高效点的sequence(可以并行运算Par)
def bsequence[A](iseq: IndexedSeq[M[A]]): M[IndexedSeq[A]] = {
if (iseq.isEmpty) unit(Vector())
else if (iseq.length == 1) map(iseq.head){Vector(_)}
else {
val (l,r) = iseq.splitAt(iseq.length / 2)
map2(bsequence(l),bsequence(r)) {_ ++ _}
}
}
def traverse[A,B](la: List[A])(f: A => M[B]): M[List[B]] = {
la.foldRight(unit(Nil: List[B])){(a,b) => map2(f(a),b){_ :: _}}
}
def replicateM[A](n: Int, ma: M[A]): M[List[A]] = {
if (n == 0) unit(Nil)
else map2(ma,replicateM(n-1,ma)) {_ :: _}
}
def factor[A,B](ma: M[A], mb: M[B]): M[(A,B)] = {
map2(ma,mb){(a,b) => (a,b)}
}
def cofactor[A,B](e: Either[M[A],M[B]]): M[Either[A,B]] = {
e match {
case Right(b) => map(b){x => Right(x)}
case Left(a) => map(a){x => Left(x)}
}
}
}

我们分别用M[A]对应List[A],Option[A]及Par[A]来分析一下sequence函数的作用:

1. sequence >>> 用map2实现 >>> 用flatMap实现:

对于List: sequence[A](lm: List[M[A]]): M[List[A]] >>> sequence[A](lm: List[List[A]]): List[List[A]]

>>> map2(list(list1),list(list2)){_ :: _} ,把封装在list里的list进行元素分拆交叉组合,

例:(List(List(1,2),List(3,4)) >>> List[List[Int]] = List(List(1, 3), List(1, 4), List(2, 3), List(2, 4))

sequence的作用体现在List.map2功能。而List.map2则是由List.flatMap实现的。所以sequence的行为还是依赖于List实例中flatMap的实 现方法

对于Option: sequence[A](lm: List[M[A]]): M[List[A]] >>> sequence[A](lm: List[Option[A]]): List[Option[A]]

>>> map2(list(opton1),list(option2)){_ :: _} ,把封装在list里的元素option值串成list,

例:(List(Some(1),Some(2),Some(3)) >>> Option[List[Int]] = Some(List(1, 2, 3))

由于sequence的行为还是依赖于实例中flatMap的实现,Option 的特点:flatMap None = None 会产生如下效果:

List(Some(1),None,Some(3)) >>> Option[List[Int]] = None

对于Par: sequence[A](lm: List[M[A]]): M[M[A]] >>> sequence[A](lm: List[Par[A]]): List[Par[A]]

>>> map2(list(par1),list(par2)){_ :: _} ,运行封装在list里的并行运算并把结果串成list,

这里Par.flatMap的功能是运行par,run(par)。这项功能恰恰是并行运算Par的核心行为。

从分析sequence不同的行为可以看出,Monad的确是一个通用概括的抽象模型。它就是一个很多数据类型组件库的软件接口:使用统一的函数名称来实现不同数据类型的不同功能效果。

与前面讨论过的Monoid一样,Monad同样需要遵循一定的法则来规范作用、实现函数组合(composition)。Monad同样需要遵循结合性操作(associativity)及恒等(identity)。

Monoid的结合性操作是这样的:op(a,op(b,c)) == op(op(a,b),c)  对Monad来说,用flatMap和map来表达结合性操作比较困难。但我们如果不从Monadic值M[A](Monadic value)而是循Monadic函数A=>M[B](Monadic function)来证明Monad结合性操作就容易多了。

A=>[B]是瑞士数学家Heinrich Kleisli法则的箭头(Kleisli Arrow)。我们可以用Kleisli Arrow来实现一个函数compose:

def compose[A,B,C](f: A=>[B], g: B=>M[C]): A=>M[C]。从函数款式看compose是一个Monadic函数组合。我们从返回值的类型A=>M[C]得出实现框架 a => ???;从传入参数类型 B=>M[C]可以估计是flatMap(M[A])(B=>M[C]; 所以:

       def compose[A,B,C](f: A => M[B], g: B => M[C]): A => M[C] = {
a => flatMap(f(a))(g)
}

注意:compose的实现还是通过了flatMap这个主导Monad实例行为的函数。有了compose我们就可以证明:

compose(f,compose(g,h)) == compose(compose(f,g),h)

flatMap和compose是互通的,可以相互转换。我们可以用compose来实现flatMap:

       def flatMapByCompose[A,B](ma: M[A])(f: A => M[B]): M[B] = {
compose((_ : Unit) => ma, f)(())
}

我们可以用例子来证明它们的互通性:

  optionMonad.flatMap(Some(12)){a => Some(a + 10)}//> res12: Option[Int] = Some(22)
optionMonad.compose((_: Unit) => Some(12), { (a : Int) => Some(a + 10)}) (())
//> res13: Option[Int] = Some(22)

至于Monad恒等性,我们已经得到了unit这个Monad恒等值:

def unit[A](a: A): M[A]。通过unit我们可以证明Monad的左右恒等:

compose(f,unit) == f

compose(unit,f) == f

由于compose是通过flatMap实现的。compose + unit也可以成为Monad最基本组件。实际上还有一组基本组件join + map + unit:

      def join[A](mma: M[M[A]]): M[A] = flatMap(mma) {ma => ma}

又是通过flatMap来实现的。

我们同样可以用join来实现flatMap和compose:

       def flatMapByJoin[A,B](ma: M[A])(f: A => M[B]): M[B] = {
join(map(ma)(f))
}
def composeByjoin[A,B,C](f: A => M[B], g: B => M[C]): A => M[C] = {
a => join(map(f(a))(g))
}

仔细观察函数款式(signature),推导并不难。map A=>M[B] >>> M[M[B]],实际上join是个展平函数M[M[A]] >>> M[A]。

虽然有三种基本组件,我还是比较倾向于flatMap,因为只要能flatMap就是Monad。对我来说Monadic programming就是flatMap programming,其中最重要的原因是scala的for-comprehension。for-comprehension是scala的特点,只要是Monad实例就可以用for-comprehension,也可以说只要能flatMap就可以吃到for-comprehension这块语法糖。我们用一个比较复杂但实用的数据类型来说明:

在前面我们曾经实现了State类型。而且我们也实现了State类型的map, flatMap这两个函数:

 case class State[S, A](run: S => (A, S)) {
def map[B](f: A => B): State[S, B] =
State(s => {
val (a, s1) = run(s)
(f(a), s1)
})
def flatMap[B](f: A => State[S, B]): State[S, B] =
State(s => {
val (a, s1) = run(s)
f(a).run(s1)
}) }

既然实现了flatMap, 那么State就可以是Monad的了吧。我们试着建一个State Monad实例:

State类定义是这样的:case class State[S,+A](run: S => (A, S))

val StateMonad = new Monad[State[???, 糟糕,Monad[M[_]],M是个接受一个类参数的高阶类型,而State[S,A]是个接受两个类参数的高阶类型,该怎么办呢?我们可以这样解释State:State[S,_]:实际上State[S,_]是一组不同S的State[A],换句话说:State不只有一个Monad实例而是一类的Monad实例。我们可以这样表述这类的Monad:

   class StateMonad[S] {
type StateS[A] = State[S,A]
val monad = new Monad[StateS] {
def unit[A](a: A): StateS[A] = State(s => (a,s))
def flatMap[A,B](ma: StateS[A])(f: A => StateS[B]): StateS[B] = flatMap(ma)(f)
}
}

我们可以这样使用以上的State Monad:StateMonad[List[Int]].monad

在上面我们遇到的问题是由于State类型与Monad M[_]类型不兼容引起的。这个问题会被scala编译器的类系统(type system)逮住,然后终止编译过程。是不是能从解决类系统问题方面着手呢?我们可以用type lambda来糊弄一下类系统:

   def StateMonad[S] = new Monad[({type StateS[A]=State[S,A]})#StateS] {
def unit[A](a: A) = State(s => (a,s))
def flatMap[A,B](sa: State[S,A])(f: A => State[S,B]): State[S,B] = flatMap(sa)(f)
}

看,在Monad类参数里糊弄了类系统后,StateMonad内部沿用了State正常表述,没任何变化。type lambda在scalaz里使用很普遍,主要还是解决了数据类型参数不匹配问题。

实现了State Monad后我们可以看个相关例子:

   val intStateMonad = StateMonad[Int]             //> intStateMonad  : ch6.state.Monad[[A]ch6.state.State[Int,A]] = ch6.state$$an
//| onfun$main$1$$anon$2@7946e1f4
def zipWithIndex[A](as: List[A]): List[(Int,A)] = {
as.foldLeft(intStateMonad.unit(List[(Int, A)]()))((acc,a) => for {
n <- getState
xs <- acc
_ <- setState(n+1)
} yield((n,a) :: xs)).run(0)._1.reverse
} //> zipWithIndex: [A](as: List[A])List[(Int, A)] val lines=List("the quick","fox is","running","and runnng","...")
//> lines : List[String] = List(the quick, fox is, running, and runnng, ...)
zipWithIndex(lines) //> res3: List[(Int, String)] = List((0,the quick), (1,fox is), (2,running), (3
//| ,and runnng), (4,...))

说明:foldLeft(z:B)(f:(B,A)=>B)的z是个intStateMonad实例类型B,所以foldLeft的操作函数就是:(intStateMonad,A)=>intStateMonad,我们可以使用for-comprehension。这个操作函数的返回结果是个intStateMonad实例;所以我们可以用State类的run(0)来运算State转换;State的状态起始值是0。

以上的例子做了些什么:它把List[String]转成了List[(Int,String)],把List[String]中每一个字串进行了索引。在这个例子里我们了解了Monad的意义:

1、可以使用for-comprehension

2、支持泛函式的循序命令执行流程,即:在高阶类结构内部执行操作流程。flatMap在这里起了关键作用,它确保了流程环节间一个环节的输出值成为另一个环境的输入值

那么我们可不可以说:Monad就是泛函编程中支持泛函方式流程式命令执行的特别编程模式。

泛函编程(24)-泛函数据类型-Monad, monadic programming的更多相关文章

  1. 泛函编程(5)-数据结构(Functional Data Structures)

    编程即是编制对数据进行运算的过程.特殊的运算必须用特定的数据结构来支持有效运算.如果没有数据结构的支持,我们就只能为每条数据申明一个内存地址了,然后使用这些地址来操作这些数据,也就是我们熟悉的申明变量 ...

  2. 泛函编程(23)-泛函数据类型-Monad

    简单来说:Monad就是泛函编程中最概括通用的数据模型(高阶数据类型).它不但涵盖了所有基础类型(primitive types)的泛函行为及操作,而且任何高阶类或者自定义类一旦具备Monad特性就可 ...

  3. 泛函编程(25)-泛函数据类型-Monad-Applicative

    上两期我们讨论了Monad.我们说Monad是个最有概括性(抽象性)的泛函数据类型,它可以覆盖绝大多数数据类型.任何数据类型只要能实现flatMap+unit这组Monad最基本组件函数就可以变成Mo ...

  4. 备份-泛函编程(23)-泛函数据类型-Monad

    泛函编程(23)-泛函数据类型-Monad http://www.cnblogs.com/tiger-xc/p/4461807.html https://blog.csdn.net/samsai100 ...

  5. 泛函编程(27)-泛函编程模式-Monad Transformer

    经过了一段时间的学习,我们了解了一系列泛函数据类型.我们知道,在所有编程语言中,数据类型是支持软件编程的基础.同样,泛函数据类型Foldable,Monoid,Functor,Applicative, ...

  6. 泛函编程(34)-泛函变量:处理状态转变-ST Monad

    泛函编程的核心模式就是函数组合(compositionality).实现函数组合的必要条件之一就是参与组合的各方程序都必须是纯代码的(pure code).所谓纯代码就是程序中的所有表达式都必须是Re ...

  7. 泛函编程(32)-泛函IO:IO Monad

    由于泛函编程非常重视函数组合(function composition),任何带有副作用(side effect)的函数都无法实现函数组合,所以必须把包含外界影响(effectful)副作用不纯代码( ...

  8. 泛函编程(28)-粗俗浅解:Functor, Applicative, Monad

    经过了一段时间的泛函编程讨论,始终没能实实在在的明确到底泛函编程有什么区别和特点:我是指在现实编程的情况下所谓的泛函编程到底如何特别.我们已经习惯了传统的行令式编程(imperative progra ...

  9. Scalaz(22)- 泛函编程思维: Coerce Monadic Thinking

    马上进入新的一年2016了,来点轻松点的内容吧.前面写过一篇关于用Reader实现依赖注入管理的博文(Scalaz(16)- Monad:依赖注入-Dependency Injection By Re ...

随机推荐

  1. php 生成 Json

    php 生成 Json 部分 <?php $arr_result = array(); //返回值 $arr_result['result'] = '0'; $arr_result['calle ...

  2. (笔记)Linux内核学习(四)之系统调用

    一 用户空间和内核空间 Linux内核将这4G字节虚拟地址空间的空间分为两部分: l  将最高的1G字节(从虚拟地址0xC0000000到0xFFFFFFFF),供内核使用,称为“内核空间”. l  ...

  3. Windows Driver Foundation-User-Mode Driver Framework 服务不能启动(错误31)问题解决

    这个错误是由于WudfPf这个服务没有启动有关,导致开机时出现SVCHOST.EXE出现,内存不能"Written"的错误, http://answers.yahoo.com/qu ...

  4. Ubuntu 16.04 LTS更新

    Canonical今天正式发布了新版的Ubuntu系统,针对PC.笔记本.上网本.平板和智能手机各类设备.这次的Ubuntu 16.04代号为Xenial Xerus——这个代号是由Canonical ...

  5. Http 1.1协议

    HTTP是hypertext transfer protocol(超文本传输协议)的简写,它是TCP/IP协议的一个应用层协议,用于定义WEB浏览器与WEB服务器之间数据交换的过程. 1.Http1. ...

  6. 关于织梦系统不支持php中GD库的问题

    大多数人在显成的PHP的CMS时,如织梦CMS,安装的时候不支持GD库,就导致整个网站的验证码不显示,以下是个人对此类问题的解决办法: 1.首先找到wamp的安装目录,找到PHP的文件夹,打开php. ...

  7. 验证 Xcode 是否来自正规渠道

    由于最近的 Xcode Ghost 事件的发生,所以我们有必要在安装完 Xcode 时验证其是否来自正规渠道.   在终端系统上运行以下命令启用检测: spctl --assess --verbose ...

  8. Xenia and Divisors

    Xenia and Divisors time limit per test 2 seconds memory limit per test 256 megabytes input standard ...

  9. ExtJs Column 显示文字内容过长 使用Tootip显示全部内容

    { text: 'Column Header Blah', dataIndex: 'blah', renderer: function(value, metaData, record, rowIdx, ...

  10. 【PRML读书笔记-Chapter1-Introduction】1.4 The Curse of Dimensionality

    维数灾难 给定如下分类问题: 其中x6和x7表示横轴和竖轴(即两个measurements),怎么分? 方法一(simple): 把整个图分成:16个格,当给定一个新的点的时候,就数他所在的格子中,哪 ...