简单来说:Monad就是泛函编程中最概括通用的数据模型(高阶数据类型)。它不但涵盖了所有基础类型(primitive types)的泛函行为及操作,而且任何高阶类或者自定义类一旦具备Monad特性就可以与任何类型的Monad实例一样在泛函编程中共同提供一套通用的泛函编程方式。所以有人把泛函编程视作Monadic Programming也不为过之。那么,具体什么是Monad呢?

在前面我们讨论过Monoid,我们说过它是一个特殊的范畴(Category),所有数据类型的Monoid实例都共同拥有一套Monoid特有的操作及遵循一套Monoid行为定律。这样我们可以把Monoid视为一个抽象数据模型,在泛函算法中使用特殊的Monoid实例就可以达到预期的效果而不需要修改算法。那么可以说Monad就是一个比Monoid更概括、更抽象、覆盖范畴更广的高阶数据类型了。

实际上在设计泛函库组件(combinator)时,我们会尽量避免重复编码,实现方式就是把通用或共性的操作抽取出来形成一些新的高阶类型(higher types),也就是新的抽象类型(Abstraction)。这样我们可以在不同的组件库中对同类操作共同使用这些通用的类型了。让我们先看看以下的一个抽象过程:

我们在前面讨论过一些数据类型。它们都有一个共同的函数:map

  1. def map[A,B](la: List[A])(f: A => B): List[B]
  2. def map[A,B](oa: Option[A])(f: A => B): Option[B]
  3. def map[A,B](pa: Par[A])(f: A => B): Par[B]
  4. def map[A,B](sa: State[S,A])(f: A => B): State[S,B]

这几个函数都具有高度相似的款式(signature),不同的是它们施用的具体数据类型。那么我们应该可以把这个map抽象出来:通过增加一个高阶类型Functor,用它来概括实现map:

  1. trait Functor[F[_]] {
  2. def map[A,B](a: F[A])(f: A => B): F[B]
  3. }

注意在上面的map例子里的施用类型都是高阶类型;List[A]、Option[A]、Par[A] ...都是F[A]这种形式。所以Functor的类参数是F[_],即: Functor[List], Functor[Option], Functor[Par] ...,这里面F[_]就是F[A],A可以是任何类型。我们可以设计一个List的Functor实例:

  1. object ListFunctor extends Functor[List] {
  2. def map[A,B](la: List[A])(f: A => B): List[B] = la map f
  3. }

把F换成List就可以了。其它类型的Functor实例:

  1. object OptionFunctor extends Functor[Option] {
  2. def map[A,B](oa: Option[A])(f: A => B): Option[B] = oa map f
  3. }
  4. object StreamFunctor extends Functor[Stream] {
  5. def map[A,B](sa: Stream[A])(f: A => B): Stream[B] = sa map f
  6. }

我们只需要对不同类型的操作使用对应的Functor实例就可以了:

  1. ListFunctor.map(List(1,2,3)){_ + 10} //> res0: List[Int] = List(11, 12, 13)
  2. OptionFunctor.map(Some(1)){_ + 10} //> res1: Option[Int] = Some(11)

操作模式是一致相同的。不过讲实在话,上面的这些实例都没什么意义,因为施用的具体类型本身就支持map。也就是说List,Option等本身就是Functor。换句话讲就是:它们都可以map,所以都是Functor。看看下面怎么使用Functor吧:

  1. trait Functor[F[_]] {
  2. def map[A,B](a: F[A])(f: A => B): F[B]
  3. def unzip[A,B](fab: F[(A,B)]): (F[A],F[B]) = {
  4. (map(fab){a => a._1},map(fab){a => a._2})
  5. }
  6. }

在这个例子中我特意把整个trait申明放了进去。这里的map还是抽象的,意味着还需要在具体的类型实例里实现。我们在设计unzip时是针对F的。在trait Functor里我们可以肯定F[(A,B)]支持map,所以我们才可以完成unzip函数的实现。这就是抽象的作用。当我们使用unzip时只要确定传入的参数fab是Functor就行了。这样unzip可以支持所有封装(A,B)的Functor:

  1. ListFunctor.unzip(List((1,10),(2,20),(3,30))) //> res0: (List[Int], List[Int]) = (List(1, 2, 3),List(10, 20, 30))
  2. OptionFunctor.unzip(Some((1,2))) //> res1: (Option[Int], Option[Int]) = (Some(1),Some(2))

讲到这里,这个Functor跟Monad有什么关系吗?不过这种抽象的目的和模式可能跟Monad有什么关联吧?那么再往下推导:在之前的数据类型设计里我们曾想碰到很多map2函数:

  1. def map2[A,B,C](la: List[A], lb: List[B])(f: (A,B) => C): List[C] = {
  2. la flatMap {a => lb map { b => f(a,b) }}
  3. }
  4. def map2[A,B,C](oa: Option[A], ob: Option[B])(f: (A,B) => C): Option[C] = {
  5. oa flatMap{a => ob map { b => f(a,b) }}
  6. }
  7. def map2[A,B,C](pa: Par[A], pb: Par[B])(f: (A,B) => C): Par[C] = {
  8. pa flatMap{a => pb map { b => f(a,b) }}
  9. }

看看这些map2函数:不但款式相同,实现方法也是相同的。不同的还是具体施用受体的数据类型。看来我们还是因为各种数据类型的不同而重复编写了map2组件。我们应该想办法一次实现map2后让所有数据类型实例都可以使用,从而彻底避免重复编码。可以肯定的是这些办法一定跟共性抽象有关。

在前面那些章节的讨论中我们一直针对某些数据类型的特性设计最基本的操作函数或组件。因为各种数据类型的不同我们重复编写了map2组件。现在我们看到map2是可以用flatMap和map来实现的。那么flatMap和map就是最基本最通用的组件了吗?事实上map可以用flatMap和unit来实现:

  1. def map[A,B](pa: Par[A])(f: A => B): Par[B] = {
  2. flatMap(pa) { a => unit(f(a)) }
  3. }

那么我们就先选择unit + flatMap作为最基本组件。当然,从前面的推导中我们可以得出unit + flatMap基本组件比Functor更抽象(更概括),因为map可以用unit + flatMap来实现。我们称这个抽象模型为Monad,它继承了Functor的特性,是Functor,因为Monad可以map。我们可以先用trait来表达Monad:

  1. trait Monad[M[_]] extends Functor[M] {
  2. def unit[A](a: A): M[A]
  3. def flatMap[A,B](ma: M[A])(f: A => M[B]): M[B]
  4. def map[A,B](ma: M[A])(f: A => B): M[B] = {
  5. flatMap(ma){a => unit(f(a))}
  6. }
  7. def map2[A,B,C](ma: M[A], mb: M[B])(f: (A,B) => C): M[C] = {
  8. flatMap(ma) { a => map(mb){ b => f(a,b) }}
  9. }
  10. }

在这个trait里unit和flatMap是抽象的。这意味着各类型的Monad实例必须实现unit和flatMap,并且会自动获取map和map2两个组件。

  1. val listMonad = new Monad[List] {
  2. def unit[A](a: A) = List(a)
  3. def flatMap[A,B](la: List[A])(f: A => List[B]): List[B] = {
  4. la flatMap f
  5. }
  6. } //> listMonad : ch11.monad.Monad[List] = ch11.monad$$anonfun$main$1$$anon$1@253
  7. //| 0c12
  8.  
  9. listMonad.map(List(1,2,3)){_ + 10} //> res0: List[Int] = List(11, 12, 13)
  10. listMonad.map2(List(1,2),List(3,4)){(a,b) => List(a,b)}
  11. //> res1: List[List[Int]] = List(List(1, 3), List(1, 4), List(2, 3), List(2, 4))
  12. //|

的确我们从listMonad中自动获得了可用的map和map2.

optionMonad是这样的:

  1. val optionMonad = new Monad[Option] {
  2. def unit[A](a: A) = Some(a)
  3. def flatMap[A,B](oa: Option[A])(f: A => Option[B]): Option[B] = {
  4. oa flatMap f
  5. }
  6. } //> optionMonad : ch11.monad.Monad[Option]{def unit[A](a: A): Some[A]} = ch11.m
  7. //| onad$$anonfun$main$1$$anon$2@4e04a765
  8. optionMonad.map(Some(1)){a => a + 10} //> res2: Option[Int] = Some(11)
  9. optionMonad.map2(Some(1),Some(2)){_ + _} //> res3: Option[Int] = Some(3)

现在我们似乎可以说任何可以flatMap(具备flatMap函数)的数据类型都是Monad。

我们可以再丰富一下现在的Monad组件库,增加多些共用组件,使Monad抽象模型能更概括实用些:

  1. trait Monad[M[_]] extends Functor[M] {
  2. def unit[A](a: A): M[A]
  3. def flatMap[A,B](ma: M[A])(f: A => M[B]): M[B]
  4. def map[A,B](ma: M[A])(f: A => B): M[B] = {
  5. flatMap(ma){a => unit(f(a))}
  6. }
  7. def map2[A,B,C](ma: M[A], mb: M[B])(f: (A,B) => C): M[C] = {
  8. flatMap(ma) { a => map(mb){ b => f(a,b) }}
  9. }
  10. def sequence[A](lm: List[M[A]]): M[List[A]] = {
  11. lm.foldRight(unit(Nil: List[A])){(a,b) => map2(a,b){_ :: _} }
  12. }
  13. def travers[A,B](la: List[A])(f: A => M[B]): M[List[B]] = {
  14. la.foldRight(unit(Nil: List[B])){(a,b) => map2(f(a),b){_ :: _}}
  15. }
  16. def replicateM[A](n: Int, ma: M[A]): M[List[A]] = {
  17. if (n == 0) unit(Nil)
  18. else map2(ma,replicateM(n-1,ma)) {_ :: _}
  19. }
  20. def factor[A,B](ma: M[A], mb: M[B]): M[(A,B)] = {
  21. map2(ma,mb){(a,b) => (a,b)}
  22. }
  23. def cofactor[A,B](e: Either[M[A],M[B]]): M[Either[A,B]] = {
  24. e match {
  25. case Right(b) => map(b){x => Right(x)}
  26. case Left(a) => map(a){x => Left(x)}
  27. }
  28. }
  29. }

可以看出,我们新增加的组件都是以unit + flatMap这两个基础组件实现的,都是更高阶的组件。所以是不是可以说Monadic programming 就是 flatMap Programming呢?

泛函编程(23)-泛函数据类型-Monad的更多相关文章

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

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

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

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

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

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

  4. 泛函编程(24)-泛函数据类型-Monad, monadic programming

    在上一节我们介绍了Monad.我们知道Monad是一个高度概括的抽象模型.好像创造Monad的目的是为了抽取各种数据类型的共性组件函数汇集成一套组件库从而避免重复编码.这些能对什么是Monad提供一个 ...

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

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

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

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

  7. 泛函编程(30)-泛函IO:Free Monad-Monad生产线

    在上节我们介绍了Trampoline.它主要是为了解决堆栈溢出(StackOverflow)错误而设计的.Trampoline类型是一种数据结构,它的设计思路是以heap换stack:对应传统递归算法 ...

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

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

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

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

随机推荐

  1. 关于BigDecimal的使用

    为什么使用BigDecimal 使用BigDecimal首先要注意到float,double是无法支持商业计算的.只能支持工程计算.即误差允许的计算.通常float占用4个字节,32位.double占 ...

  2. Mac下MySQL卸载方法 转载

    mac下mysql的DMG格式安装内有安装文件,却没有卸载文件……很郁闷的事. 网上搜了一下,发现给的方法原来得手动去删. 很多文章记述要删的文件不完整,后来在stackoverflow这里发现了一个 ...

  3. C#判断一个string是否为数字

    案一:Try...Catch(执行效率不高) private bool IsNumberic(string oText) { try { int var1=Convert.ToInt32 (oText ...

  4. 关于typedef的使用方法

    在计算机编程语言中用来为复杂的声明定义简单的别名.与宏定义有些差异.它本身是一种存储类的keyword,与auto.extern.mutable.static.register等keyword不能出如 ...

  5. iOS AVKit音视频播放全面详解

    公司项目中经常要用到音视频处理,也需要去定制一些东西,然后整理这些音视频处理就显得尤为重要!方便自己和广大朋友学习收藏! 以下参考连接特别重要: 苹果官方:AVKit API 苹果官方:AVFound ...

  6. javacc学习

    为什么要研究这些,除了个人兴趣之外,还有可以了解语言是怎样解析字符串生成逻辑代码. 他的应用性也是非常之广,如人工智能方面,把复杂的逻辑抽象成简单的文法,不懂编程的人都可以使用 说到人工智能,数据库S ...

  7. java实例练习

    1.不使用中间变量交换两个数 public class Exchange { public static void main(String[] args) { Scanner scanner = ne ...

  8. ArcGIS提取影像边界

    基于ArcGIS有多重办法可以提取影像边界,比如常用的有以下几种方式: a.System Toolboxes --> 3D Analyst Tools --> Conversion --& ...

  9. sudo: /etc/sudoers is mode 0777, should be 0440终极解决之道

    不得不说,有时候手贱的把/etc/sudoers文件权限改了,是一件很蛋疼的事.因为此时你会发现无论做什么都会弹出一条讨厌的提示,说没有权限执行等等... 网上有介绍登入root用户,或者去grub的 ...

  10. ES6 Promise 接口

    构造函数 new Promise(function(resolve, reject){}); 构造函数接受一个函数(executor)作为参数,该函数在返回 Promise 实例之前被调用.函数的两个 ...