经过了一段时间的学习,我们了解了一系列泛函数据类型。我们知道,在所有编程语言中,数据类型是支持软件编程的基础。同样,泛函数据类型Foldable,Monoid,Functor,Applicative,Traversable,Monad也是我们将来进入实际泛函编程的必需。在前面对这些数据类型的探讨中我们发现:

1、Monoid的主要用途是在进行折叠(Foldable)算法时对可折叠结构内元素进行函数施用(function application)、

2、Functor可以对任何高阶数据类型F[_]内的元素进行普通函数(A => B)施用(map)

3、Applicative extends Functor,同样都是对F[_}内元素进行函数施用。不同的是施用函数是包嵌在高阶类型的(F[A => B])。Applicative可以对所有可游览结构(Traversable),包括可折叠结构(Foldable),嵌入的元素进行函数施用。Applicative好像比Monoid功能更加强大,这样,Applicative的主要用途之一应该是对可游览结构内元素进行函数施用。

4、Monad应该是泛函编程中最重要的数据类型。Monad extends Applicative,这样,Monad就包含了Functor, Applicative的属性。更重要的是,Monad成就了for-comprehension。通过for-comprehension可以实现泛函风格的“行令编程模式(imperative programming)。泛函编程与传统的行令编程在模式上最大的分别就是在泛函编程中没有变量声明(variable declaration),变量是包嵌在一个结构里的(MyData(data)),得申明这个结构(trait MyData[String])。所以泛函编程的命令执行都是在一些结构内部进行的。Monad组件库中的组件主要支持这种结构内部运算风格。无法使用行令编程模式肯定对泛函编程过程造成诸多不便,但Monad使for-comprehension成为可能,而在for-comprehension内可以实现行令编程,所以泛函编程被称为Monadic programming并不为过。

看个for-comprehension例子:

   val compute: Option[Int] = {
for {
x <- getNextNumber
x1 <- getNextNumber
y <- shapeIt(x)
z <- divideBy(y,x1)
} yield z
}

程序在for{}内一步一步运行,典型的行令模式。

可以说:for-comprehension组成了一个嵌入式的简单行令编程语言,而产生它的Monad同时又确定了它的语意(symatics)。

以上的例子中for-comprehension是由Option[Int]定义的,那么,如果这个for-comprehension是由一个以上Monad组成的呢?例如:IO[Option[A]],这个有点像组合(Monad composition)。那么我们就先从Monad composition开始吧,看怎么把两个Monad compose起来。

怎么compose呢?先看看Functor的composition:

     trait Functor[F[_]] {
def map[A,B](fa: F[A])(f: A => B): F[B]
}
def compose[F[_],G[_]](m: Functor[F], n: Functor[G]) =
new Functor[({type l[x] = F[G[x]]})#l] {
override def map[A,B](fga: F[G[A]])(f: A => B) = {
m.map(fga)(ga => n.map(ga)(f))
}
} //> compose: [F[_], G[_]](m: ch12.ex2.Functor[F], n: ch12.ex2.Functor[G])ch12.e
//| x2.Functor[[x]F[G[x]]]

我们知道:只要实现了抽象函数map,就可以形成Functor实例。这个Functor[({type l[x] = F[G[x]]})#l]就是一个Functor实例,因为我们可以实现map[A,B](fga: F[G[A]])(f: A => B)。有了这个Functor实例,我们就可以处理F[G[A]]这样类型的数据类型:

  val listFunctor = new Functor[List] {
override def map[A,B](la: List[A])(f: A => B): List[B] = la.map(f)
} //> listFunctor : ch12.ex2.Functor[List] = ch12.ex2$$anonfun$main$1$$anon$6@3c
//| bbc1e0
val optionFunctor = new Functor[Option] {
override def map[A,B](oa: Option[A])(f: A => B): Option[B] = oa.map(f)
} //> optionFunctor : ch12.ex2.Functor[Option] = ch12.ex2$$anonfun$main$1$$anon$
//| 7@35fb3008 Option("abc").map(_.length) //> res4: Option[Int] = Some(3)
val fg = compose(listFunctor,optionFunctor) //> fg : ch12.ex2.Functor[[x]List[Option[x]]] = ch12.ex2$$anonfun$main$1$$anon
//| $5@7225790e fg.map(List(Option("abc"),Option("xy"),Option("ryuiyty"))){ _.length }
//> res5: List[Option[Int]] = List(Some(3), Some(2), Some(7))

在以上我们用listFunctor处理了List[A]类型数据,optionFunctor处理Option[A]。最终我们用fg处理像List[Option[String]]类型的数据。

那么我们如果能实现Monad[M[N]]的flatMap不就能得到这个Monad实例了嘛:

   def composeM[M[_],N[_](m: Monad[M], n: Monad[N]): Monad[({type l[x] = M[N[x]]})#l]= {
new Monad[({type l[x] = M[N[x]]})#l] {
def flatMap[A,B](mna: M[N[A]])(f: A => M[N[B]]): M[N[B]] = {
????? !!!!!
}
}
}

可悲的是这次无法实现flatMap。这个悲剧明确了推论“Monad do not compose!”。那我们的Monadic语言梦想就这么快幻灭了吗?实际上多个Monad定义的for-comprehension可以通过Monad Transformer来实现。Monad Transformer可以实现多个Monad效果的累加(stacking effect)。好,那我们就开始看看这个Monad Transformer吧:

我们先实现一个Maybe Monad:

Maybe就是Option。由于scala标准库里已经有Option类型,为免函数引用混扰,所以定义一个新的Monad。

     trait Functor[F[_]] {
def map[A,B](fa: F[A])(f: A => B): F[B]
}
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]
}
trait Maybe[+A] {
def map[B](f: A => B): Maybe[B] ={
this match {
case Just(a) => Just(f(a))
case _ => Nada
}
}
def flatMap[B](f: A => Maybe[B]): Maybe[B] = {
this match {
case Just(a) => f(a)
case _ => Nada
}
}
}
case class Just[+A](a: A) extends Maybe[A]
case object Nada extends Maybe[Nothing]

我们实现了Maybe类型的unit,map,flatMap,所以我们可以在Maybe Monad的环境里实现for-comprehension的应用

     val maybeFor: Maybe[Int] = for {
x <- Just(2)
y <- Just(5)
z = x * y
} yield z //> maybeFor : ch12.ex2.Maybe[Int] = Just(10)

我们看到了一段嵌在for-comprehension内的行令运算。但运算的环境要求从表面上还无法明确。那么,这段运算的另一个版本可能有所启示:

     val maybeMap: Maybe[Int] = {
Just(2).flatMap(x => Just(5).map(y => x * y))
//> maybeMap : ch12.ex2.Maybe[Int] = Just(10)
}

我们知道for-comprehension就是flatMap的方法糖。所以以上就是原始flatMap运算。从这个flatMap表达形式我们可以得出每一句运算都必须遵循主导Monad的flatMap函数类型(signature),也就是说类型必须匹配。

我们再来一个熟悉的Monad,State Monad:

     type State[S,+A] = S => (A,S)
object State {
def getState[S]: State[S,S] = s => (s,s)
def setState[S](s: S): State[S,Unit] = _ => ((),s)
}
class StateOps[S,A](sa: State[S,A]) {
def unit(a: A) = (s: S) => (a,s)
def map[B](f: A => B): State[S,B] = {
s => {
val (a,s1) = sa(s)
(f(a),s1)
}
}
def flatMap[B](f: A => State[S,B]): State[S,B] = {
s => {
val (a,s1) = sa(s)
f(a)(s1)
}
}
def getState[S]: State[S,S] = s => (s,s)
def setState[S](s: S): State[S,Unit] = _ => ((),s)
}
implicit def toStateOps[S,A](sa: State[S,A]) = new StateOps(sa)
//> toStateOps: [S, A](sa: ch12.ex2.State[S,A])ch12.ex2.StateOps[S,A]

同样我们可以用State Monad定义的for-comprehension进行行令编程:

     import State._
val stateFor: State[Int, Int] = for {
x <- getState[Int]
y = x * 5
_ <- setState(x+1)
} yield y //> stateFor : ch12.ex2.State[Int,Int] = <function1> stateFor(2) //> res0: (Int, Int) = (10,3) 可以肯定这个State Monad for-comprehension内的行令运算同样需要遵循State Monad map, flatMap的类型匹配。

可以肯定这个State Monad for-comprehension内的行令运算同样需要遵循State Monad map, flatMap的类型匹配。

那我们下面把这两个Monad在一个for-comprehension里运行。比如

  val nocompileFor = {
def remainder(a: Int, b: Int): Maybe[Int] = {
a % b match {
case 0 => Nada
case r => Just(r)
}
}
for {
x <- getState[Int] //State.flatMap
y <- remainder(x,2) //Maybe.flatMap
z = x + y //???.map
_ <- setState[Int](5) //State.flatMap
} yield y
}

可以看的出来,flatMap的类型都乱了套了。以上例子无法通过编译器。

解决方案:Monad Transformer:

上面的失败例子是要解决State[Maybe[A]]这种类型的问题。我们就需要一个State Monad Transformer:

  import StateT._
trait StateT[M[_],S,A] { // State Monad Transformer
def apply(s: S): M[(A,S)] def map[B](f: A => B)(implicit m: Functor[M]): StateT[M,S,B] = {
stateT( s => m.map(apply(s)){
case (a,s1) => (f(a),s1)
})
} def flatMap[B](f: A => StateT[M,S,B])(implicit m: Monad[M]): StateT[M,S,B] = {
stateT( s => m.flatMap(apply(s)){
case (a,s1) => f(a)(s1)
})
} }
object StateT {
def stateT[M[_],S,A](f: S => M[(A,S)]): StateT[M,S,A] = {
new StateT[M,S,A] {
def apply(s: S) = f(s)
}
}
def liftM[M[_],S,A](ma: M[A])(implicit m: Monad[M]): StateT[M,S,A] = {
stateT(s => m.map(ma)(a => (a, s)))
}
}

StateT是个State Monad Transformer,同时StateT也是一个Monad实例,因为我们可以实现它的flatMap函数。既然StateT是个Monad实例,那我们就可以用StateT来定义它的for-comprehension了:

   val maybeState: StateT[Maybe,Int,Int] = {
def getState[S]: StateT[Maybe,S,S] = stateT(s => Just((s,s)))
def setState[S](s: S): StateT[Maybe,S,Unit] = stateT(s1 => Just(((),s)))
def remainder(a: Int, b: Int): Maybe[Int] = {
a % b match {
case 0 => Nada
case r => Just(r)
}
}
for {
x <- getState[Int]
y <- liftM[Maybe,Int,Int](remainder(x,2))
z = x + y
_ <- setState[Int](5)
} yield y
} //> maybeState : ch12.ex2.StateT[ch12.ex2.Maybe,Int,Int] = ch12.ex2$$anonfun$m
//| ain$1$StateT$3$$anon$4@34b7bfc0 maybeState(1) //> res1: ch12.ex2.Maybe[(Int, Int)] = Just((1,5))
maybeState(0) //> res2: ch12.ex2.Maybe[(Int, Int)] = Nada

以上这个for-comprehension是用StateT[Maybe,Int,Int]来定义的。那么所有在for-comprehension内的表达式右方就必须是StateT类型。上面的getState,setState函数结果都是StateT类型,但remainder函数返回结果却是Maybe类型。所以我们用liftM把Maybe类型升格到StateT类型。liftM的函数定义如下:

       def liftM[M[_],S,A](ma: M[A])(implicit m: Monad[M]): StateT[M,S,A] = {
stateT(s => m.map(ma)(a => (a, s)))
}

liftM的作用就是把一个Monad M[A]升格成为StateT。上面的例子我们用liftM把Monad Maybe升格成StateT类型,这样整个for-comprehension内部所有表达式类型都匹配了。注意StateT把State Monad和任何其它一个Monad合起来用:上面的例子用了Maybe。实际上StateT[M,S,A]里的M可以是Maybe也可以是Option,Either,Validation。。。那我们就可以得到StateT[Option,Int,Int],StateT[Either,Int,Int]这些Monad Transformer并在for-comprehension里体现这些组成Monad的效果。更重要的是StateT是个Monad那么我们可以把它当作任何其它Monad一样与其它Monad结合形成新的Monad Transformer。

如果我们需要处理相反的类型:Maybe[State],我们就需要定义MaybeT。我们先看看MaybeT的类型款式:

caseclass MaybeT[M[_],A](run: M[Maybe[A]]) 这是Monad Transformer通用款式

我们把共同使用的Monad包嵌在参数里:

     case class MaybeT[M[_],A](run: M[Maybe[A]]) {
def map[B](f: A => B)(implicit m: Functor[M]): MaybeT[M,B] = {
MaybeT[M,B](m.map(run)(a => a map f))
}
def flatMap[B](f: A => MaybeT[M,B])(implicit m: Monad[M]): MaybeT[M,B] = {
MaybeT[M,B](m.flatMap(run) {
case Just(a) => f(a).run
case Nada => m.unit(Nada)
})
}
}

如果用Option作为主导Monad,那么我们可以设计一个Option的Monad Transformer OptionT类型:

   case class OptionT[M[_],A](run: M[Option[A]]) {
def map[B](f: A => B)(implicit m: Functor[M]): OptionT[M,B] = {
OptionT[M,B](m.map(run)(a => a.map(f)))
}
def flatMap[B](f: A => OptionT[M,B])(implicit m: Monad[M]): OptionT[M,B] = {
OptionT[M,B](m.flatMap(run) {
case Some(a) => f(a).run
case None => m.unit(None)
})
}
}

无论如何,只要我们能够把共同使用的这两个Monad升格成目标Monad Transformer类型格式就可以放心在for-comprehension中进行行令编程了。

泛函编程(27)-泛函编程模式-Monad Transformer的更多相关文章

  1. JavaScript异步编程助手:Promise模式

    :Promises是一种令代码异步行为更加优雅的抽象,它很有可能是JavaScript的下一个编程范式,一个Promise即表示任务结果,无论该任务是否完成. 异步模式在Web编程中变得越来越重要,对 ...

  2. node.js异步编程的几种模式

    Node.js异步编程的几种模式 以读取文件为例: 1.callback function const fs = require('fs'); //callback function fs.readF ...

  3. javascript编程单线程之同步模式

    javascript编程单线程之同步模式 主流的js 环境都是单线程吗模式执行js 代码, js采用为单线程的原因与最开始设计初衷有关,最早是运行在浏览器端的脚本语言,目的是为了实现页面上的动态交互, ...

  4. 深圳scala-meetup-20180902(3)- Using heterogeneous Monads in for-comprehension with Monad Transformer

    scala中的Option类型是个很好用的数据结构,用None来替代java的null可以大大降低代码的复杂性,它还是一个更容易解释的状态表达形式,比如在读取数据时我们用Some(Row)来代表读取的 ...

  5. Python交互式编程导论----事件驱动编程

    传统的编程是如下线性模式的: 开始--->代码块A--->代码块B--->代码块C--->代码块D--->......--->结束 每一个代码块里是完成各种各样事情 ...

  6. 物联网网络编程、Web编程综述

    本文是基于嵌入式物联网研发工程师的视觉对网络编程和web编程进行阐述.对于专注J2EE后端服务开发的童鞋们来说,这篇文章可能稍显简单.但是网络编程和web编程对于绝大部分嵌入式物联网工程师来说是一块真 ...

  7. .net 系列:并发编程之一 并发编程的初步理论

    一.关于并发编程的几个误解 1)并发就是多线程 实际上多线程只是并发编程的一种形式而已,在C#中还有很多其他的并发编程技术,包括异步编程,并行编程,TPL数据流,响应式编程等.  2)只有大型服务器才 ...

  8. 【憩园】C#并发编程之异步编程(一)

    写在前面 C#5.0中,对异步编程进行了一次革命性的重构,引入了async和await这两个关键字,使得开发人员在不需要深刻了解异步编程的底层原理,就可以写出十分优美而又代码量极少的代码.如果使用得当 ...

  9. 如何用ABP框架快速完成项目(面向项目交付编程面向客户编程篇)(1) - 目录

    昨天发表了<如何用ABP框架快速完成项目 - 自动化测试 - 前端angular e2e protractor>后,大家十分热情,几个小时内就收到了不少问题,包括: 对于ui自动化测试这方 ...

随机推荐

  1. MVC3中如何输出富文本

    MVC3中如何输出富文本 在网站的文本输出中,经常会将DB里的文本输出到页面上. 一般来说是直接利用MVC3中的ViewBag将文本带到前台并表示, 或是是直接以<%:model.data%&g ...

  2. Liferay7 BPM门户开发之44: 集成Activiti展示流程列表

    处理依赖关系 集成Activiti之前,必须搞清楚其中的依赖关系,才能在Gradle里进行配置. 依赖关系: 例如,其中activiti-engine依赖于activiti-bpmn-converte ...

  3. ios app 打包上传 app store(Application Loader)

    背景:使用Xcode 上传APP, 这个 有时候很慢,构建版本需要等很长时间,所以我推荐使用Application Loader 1.使用xocd 打包,导出.ipa文件 2. OK ,跟着上面做, ...

  4. css多行文本省略号问题

    已知,单行文本溢出内容用省略号代替,css代码如下: text-overflow: ellipsis; 溢出部分用...代替 white-space: nowrap; //强制在一行显示 overfl ...

  5. RoundedImageView,实现圆形、圆角矩形的注意事项

    RoundedImageView是gitHub上面的一个开源组件(https://github.com/vinc3m1/RoundedImageView),实现一些圆形或者圆角矩形是很方便的, < ...

  6. C# LINQ需求实现演化

    Linq是C#3.0引入的,在C#2.0实现从集合中过滤符合条件的记录实现方式. 假设有一个Book类,以及一个Book类的集合,现在需要从集合中查找出单价大于50的Book. 1.固定查询字段的实现 ...

  7. poj2513Colored Sticks(无向图的欧拉回路)

    /* 题意:将两端涂有颜色的木棒连在一起,并且连接处的颜色相同! 思路:将每一个单词看成一个节点,建立节点之间的无向图!判断是否是欧拉回路或者是欧拉路 并查集判通 + 奇度节点个数等于2或者0 */ ...

  8. Yii的学习(3)--查询生成器 (Query Builder)

    原文地址:http://www.yiiframework.com/doc/guide/1.1/en/database.query-builder 不过原文是英文的,Yii的官网没有翻译这一章,自己就尝 ...

  9. Android基于mAppWidget实现手绘地图(十二)–显示当前用户位置

    若显示当前用户位置,你需要执行以下步骤: 1.添加ACCESS_COARSE_LOCATION 和ACCESS_FINE_LOCATION权限在AndroidManifest.xml中 2.用mapW ...

  10. JIRA简介

    JIRA是Atlassian公司出品的项目与事务跟踪工具,被广泛应用于缺陷跟踪.客户服务.需求收集.流程审批.任务跟踪.项目跟踪和敏捷管理等工作领域,其配置灵活.功能全面.部署简单.扩展丰富.“Jir ...