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

    经过了一段时间的学习,我们了解了一系列泛函数据类型。我们知道,在所有编程语言中,数据类型是支持软件编程的基础。同样,泛函数据类型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例子:

1   val compute: Option[Int] = {
2    for {
3        x <- getNextNumber
4        x1 <- getNextNumber
5        y <- shapeIt(x)
6        z <- divideBy(y,x1)
7    } yield z
8   }

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

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

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

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

 1     trait Functor[F[_]] {
 2         def map[A,B](fa: F[A])(f: A => B): F[B]
 3     }
 4   def compose[F[_],G[_]](m: Functor[F], n: Functor[G]) =
 5       new Functor[({type l[x] = F[G[x]]})#l] {
 6           override def map[A,B](fga: F[G[A]])(f: A => B) = {
 7               m.map(fga)(ga => n.map(ga)(f))
 8           }
 9       }                                           //> compose: [F[_], G[_]](m: ch12.ex2.Functor[F], n: ch12.ex2.Functor[G])ch12.e
10                                                   //| 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]]这样类型的数据类型:

 1  val listFunctor = new Functor[List] {
 2       override def map[A,B](la: List[A])(f: A => B): List[B] = la.map(f)
 3   }                                               //> listFunctor  : ch12.ex2.Functor[List] = ch12.ex2$$anonfun$main$1$$anon$6@3c
 4                                                   //| bbc1e0
 5   val optionFunctor = new Functor[Option] {
 6       override def map[A,B](oa: Option[A])(f: A => B): Option[B] = oa.map(f)
 7   }                                               //> optionFunctor  : ch12.ex2.Functor[Option] = ch12.ex2$$anonfun$main$1$$anon$
 8                                                   //| 7@35fb3008
 9   
10   Option("abc").map(_.length)                     //> res4: Option[Int] = Some(3)
11   val fg = compose(listFunctor,optionFunctor)     //> fg  : ch12.ex2.Functor[[x]List[Option[x]]] = ch12.ex2$$anonfun$main$1$$anon
12                                                   //| $5@7225790e
13   
14   fg.map(List(Option("abc"),Option("xy"),Option("ryuiyty"))){ _.length }
15                                                   //> 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实例了嘛:

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

可悲的是这次无法实现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。

 1     trait Functor[F[_]] {
 2         def map[A,B](fa: F[A])(f: A => B): F[B]
 3     }
 4     trait Monad[M[_]] extends Functor[M] {
 5         def unit[A](a: A): M[A]
 6         def flatMap[A,B](ma: M[A])(f: A => M[B]): M[B]
 7     }    
 8     trait Maybe[+A] {
 9         def map[B](f: A => B): Maybe[B] ={
10             this match {
11                 case Just(a) => Just(f(a))
12                 case _ => Nada
13             }
14         }
15         def flatMap[B](f: A => Maybe[B]): Maybe[B] = {
16             this match {
17                 case Just(a) => f(a)
18                 case _ => Nada
19             }
20         }
21     }
22     case class Just[+A](a: A) extends Maybe[A]
23     case object Nada extends Maybe[Nothing]

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

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

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

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

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

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

 1     type State[S,+A] = S => (A,S)
 2   object State {
 3       def getState[S]: State[S,S] = s => (s,s)
 4       def setState[S](s: S): State[S,Unit] = _ => ((),s)
 5   }
 6     class StateOps[S,A](sa: State[S,A]) {
 7         def unit(a: A) = (s: S) => (a,s)
 8         def map[B](f: A => B): State[S,B] = {
 9             s => {
10                 val (a,s1) = sa(s)
11                 (f(a),s1)
12             }
13         }
14         def flatMap[B](f: A => State[S,B]): State[S,B] = {
15             s => {
16                 val (a,s1) = sa(s)
17                 f(a)(s1)
18             }
19         }
20     def getState[S]: State[S,S] = s => (s,s)
21     def setState[S](s: S): State[S,Unit] = _ => ((),s)
22     }
23     implicit def toStateOps[S,A](sa: State[S,A]) = new StateOps(sa)
24                                                   //> toStateOps: [S, A](sa: ch12.ex2.State[S,A])ch12.ex2.StateOps[S,A]

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

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

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

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

 1  val nocompileFor = {
 2       def remainder(a: Int, b: Int): Maybe[Int] = {
 3           a % b match {
 4               case 0 => Nada
 5               case r => Just(r)
 6           }
 7       }
 8       for {
 9           x <- getState[Int]    //State.flatMap
10           y <- remainder(x,2)   //Maybe.flatMap
11           z = x + y             //???.map
12           _ <- setState[Int](5) //State.flatMap
13       } yield y
14   }                                               

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

解决方案:Monad Transformer:

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

 1  import StateT._
 2     trait StateT[M[_],S,A] {   // State Monad Transformer
 3       def apply(s: S): M[(A,S)]
 4       
 5         def map[B](f: A => B)(implicit m: Functor[M]): StateT[M,S,B] = {
 6             stateT( s => m.map(apply(s)){
 7                 case (a,s1) => (f(a),s1)
 8             })
 9         }
10         
11         def flatMap[B](f: A => StateT[M,S,B])(implicit m: Monad[M]): StateT[M,S,B] = {
12             stateT( s => m.flatMap(apply(s)){
13                 case (a,s1) => f(a)(s1)
14             })
15         }
16         
17     }
18   object StateT {
19       def stateT[M[_],S,A](f: S => M[(A,S)]): StateT[M,S,A] = {
20           new StateT[M,S,A] {
21               def apply(s: S) = f(s)
22           }
23       }
24       def liftM[M[_],S,A](ma: M[A])(implicit m: Monad[M]): StateT[M,S,A] = {
25             stateT(s => m.map(ma)(a => (a, s)))
26       }
27   }

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

 1   val maybeState: StateT[Maybe,Int,Int] = {
 2     def getState[S]: StateT[Maybe,S,S] = stateT(s => Just((s,s)))
 3     def setState[S](s: S): StateT[Maybe,S,Unit] = stateT(s1 => Just(((),s)))
 4       def remainder(a: Int, b: Int): Maybe[Int] = {
 5           a % b match {
 6               case 0 => Nada
 7               case r => Just(r)
 8           }
 9       }
10       for {
11           x <- getState[Int]
12           y <- liftM[Maybe,Int,Int](remainder(x,2))
13           z = x + y
14           _ <- setState[Int](5)
15       } yield y
16   }                                               //> maybeState  : ch12.ex2.StateT[ch12.ex2.Maybe,Int,Int] = ch12.ex2$$anonfun$m
17                                                   //| ain$1$StateT$3$$anon$4@34b7bfc0
18  
19   maybeState(1)                                   //> res1: ch12.ex2.Maybe[(Int, Int)] = Just((1,5))
20   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的函数定义如下:

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

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包嵌在参数里:

 1     case class MaybeT[M[_],A](run: M[Maybe[A]]) {
 2         def map[B](f: A => B)(implicit m: Functor[M]): MaybeT[M,B] = {
 3             MaybeT[M,B](m.map(run)(a => a map f))
 4         }
 5         def flatMap[B](f: A => MaybeT[M,B])(implicit m: Monad[M]): MaybeT[M,B] = {
 6             MaybeT[M,B](m.flatMap(run) {
 7                 case Just(a) => f(a).run
 8                 case Nada => m.unit(Nada)
 9             })
10         }
11     }

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

 1   case class OptionT[M[_],A](run: M[Option[A]]) {
 2       def map[B](f: A => B)(implicit m: Functor[M]): OptionT[M,B] = {
 3            OptionT[M,B](m.map(run)(a => a.map(f)))
 4       }
 5       def flatMap[B](f: A => OptionT[M,B])(implicit m: Monad[M]): OptionT[M,B] = {
 6           OptionT[M,B](m.flatMap(run) {
 7               case Some(a) => f(a).run
 8               case None => m.unit(None)
 9           })
10       }
11   }

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

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏java 成神之路

Trie Tree 实现中文分词器

33911
来自专栏函数式编程语言及工具

Scalaz(12)- Monad:再述述flatMap,顺便了解MonadPlus

  在前面的几篇讨论里我们初步对FP有了些少了解:FP嘛,不就是F[A]吗?也是,FP就是在F[]壳子(context)内对程序的状态进行更改,也就是在F壳子(...

1797
来自专栏函数式编程语言及工具

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

    简单来说:Monad就是泛函编程中最概括通用的数据模型(高阶数据类型)。它不但涵盖了所有基础类型(primitive types)的泛函行为及操作,而且...

1738
来自专栏草根专栏

使用 C#/.NET Core 实现单体设计模式

本文的概念内容来自深入浅出设计模式一书 由于我在给公司做内培, 所以最近天天写设计模式的文章.... 单体模式 Singleton 单体模式的目标就是只创建一个...

3075
来自专栏静默虚空的博客

Java正则速成秘籍(一)之招式篇

导读 正则表达式是什么?有什么用? 正则表达式(Regular Expression)是一种文本规则,可以用来校验、查找、替换与规则匹配的文本。 又爱又恨的正...

1718
来自专栏大内老A

字符串的驻留(String Interning)

关于字符串的驻留的机制,对于那些了解它的人肯定会认为很简单,但是我相信会有很大一部分人对它存在迷惑。在开始关于字符串的驻留之前,先给出一个有趣的Sample: ...

1666
来自专栏云端漫步

go设计模式之建造者模式

func NewBuilder(build Builder) *Director {

583
来自专栏醉生梦死

java抓取豆瓣电影数据,分析电影评分,生成统计图表 ---servlet

1224
来自专栏函数式编程语言及工具

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

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

1787
来自专栏Android知识点总结

Java总结IO之总集篇

字符流和字节流向来各行其事,很少有交集。 但Reader和Writer有两个奇子,名叫InputStreamReader(男)和OutputStreamWri...

1065

扫码关注云+社区