设为首页 加入收藏

TOP

深圳scala-meetup-20180902(3)- Using heterogeneous Monads in for-comprehension with Monad Transformer(一)
2019-08-15 00:11:28 】 浏览:139
Tags:深圳 scala-meetup-20180902 Using heterogeneous Monads for-comprehension with Monad Transformer

  scala中的Option类型是个很好用的数据结构,用None来替代java的null可以大大降低代码的复杂性,它还是一个更容易解释的状态表达形式,比如在读取数据时我们用Some(Row)来代表读取的数据行Row,用None来代表没有读到任何数据,免去了null判断。由此我们可以对数据库操作的结果有一种很直观的理解。同样,我们又可以用Either的Right(Row)来代表成功运算获取了结果Row,用Left(Err)代表运算产生了异常Err。对于数据库编程我还是选择了Task[Either[E,Option[A]]]这种类型作为数据库操作运算的统一类型。可以看到这是一个复合类型:首先Task是一个non-blocking的运算结果类型,Either[E,Option[A]]则同时可以处理发生异常、获取运算结果、无法获取结果几种状态。我觉着这样已经足够代表数据库操作状态了。

  在Task[Either[E,Option[A]]]这个复合类型中的组成类型Option[A],Either[E,A]实际上是包嵌A类型元素的不同管道,各自可以独立支持Monadic编程,如下:

object session2 extends App { val value: Option[Int] = Some(10) def add(a: Int, b: Int): Option[Int] = Some(a+b) val p = for { a <- value b <- add(a, 3) _ <- None c <- add(a,b) } yield a println(p) // None
 } object session21 extends App { val value: Either[String,Int] = Right(10) def add(a: Int, b: Int): Either[String,Int] = Right(a+b) val p = for { a <- value b <- add(a, 3) _ <- Left("oh no ...") c <- add(a,b) } yield c println(p) //Left("oh no ...")

如果我们把这两个类型在for-comprehension里结合使用:

object session22 extends App { val ovalue: Option[Int] = Some(10) val eva lue: Either[String,Int] = Right(10) val p = for { a <- ovalue b <- eva lue c = a * b } yield c println(p) } Error:(39, 7) type mismatch; found : scala.util.Either[String,Int] required: Option[?] b <- eva lue

无法通过编译!当然,这是因为Option,Either是不同的Monad。如果我们把这两个Monad结合形成一个复合的类型,那么用for-comprehension应该没什么问题,如下:

object session23 extends App { def combined(int i): Task[Either[String,Option[Int]] = ??? val p = for { a <- combined(2) b <- combined(3) c = a * b } yield c println(p) //Task(Right(5))
 }

我们可能需要通过函数组合来构建这个复合类型。通过证明,Functor是可以实现函数组合的,如下:

object session4 extends App { 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)) } val optionInList = List(Some("1"),Some("22"),Some("333")) val optionInListFunctor = composeFunctor(Functor[List],Functor[Option]) val strlen: String => Int = _.length println(optionInListFunctor.map(optionInList)(strlen)) } //List(Some(1), Some(2), Some(3))

以上代码证明Functor[M]可以通过函数组合和Functor[N]形成Functor[M[N]]。好像这正是我们需要对两个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 pure[A](a: => A) = ma.point(mb.pure(a)) def bind[A,B](mab: M[N[A]])(f: A => M[N[B]]): M[N[B]] =
           ??? ... }

因为我们无法实现组合后的Monad特质函数bind,所以这条路走不通了。不过cats函数组件库提供了OptionT,EitherT这两个Monad Transformer,它们的类型款式如下:

final case class OptionT[F[_], A](value: F[Option[A]]) {...} inal case class EitherT[F[_], A, B](value: F[Either[A, B]]) {...} //包嵌类型
OptionT[Task,A] => Task[Option[A]] EitherT[Task,A,B] => Task[Either[A,B]] //多层套嵌
Task[Either[E,Option[A]]] => OptionT[EitherT[Task,E,A],A]

Monad Transformer包嵌的类型正是我们需要的类型,我们可以用Task来代表F[_]。实际上EitherT也可以被视为一种F[_],所以从OptionT[EitherT[Task,E,A],A]可以得到Task[Either[E,Option[A]]]。注意复合型Monad Transformer的组成是由内向外反向的:Option[A]是最内的元素,那么在合成时就摆在最外。下面我们就用type定义简化整个描述:

  type DBOError[A] = EitherT[Task,String,A] type DBOResult[A] = OptionT[DBOError,A]

这样表示就清楚多了,这个DBORes

首页 上一页 1 2 下一页 尾页 1/2/2
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇Scala集合学习总结 下一篇scala集合与java集合的转换应用

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目