设为首页 加入收藏

TOP

Scalaz(25)- Monad: Monad Transformer-叠加Monad效果(一)
2017-10-10 12:13:21 】 浏览:7719
Tags:Scalaz Monad Transformer- 叠加 效果

  中间插播了几篇scalaz数据类型,现在又要回到Monad专题。因为FP的特征就是Monad式编程(Monadic programming),所以必须充分理解认识Monad、熟练掌握Monad运用。曾经看到一段对Monad的描述:“Monadic for-comprehension就是一种嵌入式编程语言,由它的Monad提供它的语法”。但如果每一种Monad的for-comprehension都独立提供一套语法的话,这种编程语言就显得十分单调、功能简单了。那么既然是FP,我们应该可以通过函数组合(function composition)来把很多简单的for-comprehension组合成一套完善的编程语言吧?比如这样:Option[A] >>> IO[Option[A]] >>> IO[Either[String,Option[A]]。恰恰,Monad是不支持函数组合的。先了解一下函数组合:Functor是可以组合的,我们可以把fa和fb组合成一个更复杂的Functor fab,我们来验证一下:

 def composeFunctor[M[_],N[_]](fa: Functor[M], fb: Functor[N]): Functor[({type mn[x] = M[N[x]]})#mn] =
   new Functor[({type mn[x] = M[N[x]]})#mn] { def map[A,B](fab: M[N[A]])(f: A => B): M[N[B]] = fa.map(fab)(n => fb.map(n)(f)) } //> composeFunctor: [M[_], N[_]](fa: scalaz.Functor[M], fb: scalaz.Functor[N])s //| calaz.Functor[[x]M[N[x]]]

我们来解释一下:如果M,N都是Functor,那么M[N[A]]也是Functor,我们可以用M[N[A]].map来运算A值。看看下面的例子:

1 val stringlen: String => Int = _.length           //> stringlen : String => Int = <function1>
2 val optionInList = List("1".some,"12".some,"123".some) 3                                                   //> optionInList : List[Option[String]] = List(Some(1), Some(12), Some(123))
4 
5 val mnFunctor = composeFunctor(Functor[List],Functor[Option]) 6                                                   //> mnFunctor : scalaz.Functor[[x]List[Option[x]]] = Exercises.monadtrans$$ano 7                                                   //| nfun$main$1$$anon$1@130d63be
8 mnFunctor.map(optionInList)(stringlen)            //> res3: List[Option[Int]] = List(Some(1), Some(2), Some(3))

那么我们需要的Monad组合应该是这样的:M[N[A]],M,N都是Monad,如:Either[String,Option[A]],甚至是M[N[P[A]]],三层Monad。可惜,不是所有Monad都支持函数组合的,看下面:

 def composeMonad[M[_],N[_]](ma: Monad[M], mb: Monad[N]): Monad[({type mn[x] = M[N[x]]})#mn] =
   new Monad[({type mn[x] = M[N[x]]})#mn] { def point[A](a: => A) = ma.point(mb.point(a)) def bind[A,B](mab: M[N[A]])(f: A => M[N[B]]): M[N[B]] =
           ??? ... }

实现M[N[A]].bind是不可能的,大家可以试试。这就堵死了函数组合这条路。难道我们就无法使用M[N[A]]这样的for-comprehension了吗?毕竟像Either[String,Option[A]]这样的组合是很普遍的啊,比如说从数据库里读取这样的动作,有几种可能:取得数据、无数据None、发生错误。无论如何我们先试试用for-comprehension:

1 type Result[A] = String \/ Option[A] 2 val result: Result[Int] = 62.some.right           //> result : Exercises.monadtxns.Result[Int] = \/-(Some(62))
3 for { 4     optionValue <- result 5 } yield { 6   for { 7       valueA <- optionValue 8   } yield valueA + 18                             //> res0: scalaz.\/[String,Option[Int]] = \/-(Some(80))
9 }

从上面可以了解我们必须用两层for-comprehension才能运算A值。那么可想而知如果是M[N[P[A]]]就需要三层for-comprehension了。这就是所谓的“下阶梯式算法”(stair-stepping)。表面上来看stair-stepping会产生复杂臃肿的代码,丧失FP的精简优雅风格。但想深一层,如果其中一个Monad是会产生副作用的如IO[Option[A]],那么上面的例子就变成这样:

1 for { 2   optionData <- IO 3 } yield { 4   for { 5     data <- optionData 6   } yield Process(data) 7 }

我们看到在第一层运算里进行了IO运算,产生了副作用。那么以上的代码就不再是纯代码了,无法保障函数组合。也就是说stair-stepping会产生不纯代码,违背了FP要求。之前我们曾经讨论过 ReaderWriterState Monad,它是Reader,Writer,State三个Monad的组合。在它的for-comprehension里的运算结果类型是ReaderWriterState一种,所以没有stair-stepping忧虑。但我们必须先创建一个新的类型(不是通过函数组合的新类型)。难道我们在使用不同要求的for-comprehension时都需要重新创建一个新类型吗,这样不就损失了FP的代码重复使用特点了吗?不,scalaz提供的Monad Transformer就是一个有效的解决方案。

scalaz为很多type class提供了Monad Transformer,它们都以T尾缀命名如OptionT、EitherT、StateT...,我们可以通过Monad Transformer来灵活地组合Monad。以OptionT为例:

1 type Error[A] = \/[String, A] 2 type Result[A] = OptionT[
首页 上一页 1 2 3 4 5 下一页 尾页 1/5/5
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇Scalaz(26)- Lens: 函数式不.. 下一篇Scalaz(27)- Inference & Unap..

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目