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()
def add(a: Int, b: Int): Option[Int] = Some(a+b) val p = for {
a <- value
b <- add(a, )
_ <- None
c <- add(a,b)
} yield a println(p) // None } object session21 extends App {
val value: Either[String,Int] = Right()
def add(a: Int, b: Int): Either[String,Int] = Right(a+b) val p = for {
a <- value
b <- add(a, )
_ <- 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()
val evalue: Either[String,Int] = Right() val p = for {
a <- ovalue
b <- evalue
c = a * b
} yield c println(p) } Error:(, ) type mismatch;
found : scala.util.Either[String,Int]
required: Option[?]
b <- evalue

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

object session23 extends App {
def combined(int i): Task[Either[String,Option[Int]] = ???
val p = for {
a <- combined()
b <- combined()
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(""),Some(""),Some(""))
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]

这样表示就清楚多了,这个DBOResult[A]就是我们需要对付的类型。剩下来的工作就是需要提供一些类型转换函数,分别把A,Option[A],Either[String,A],Task[A]都转换成DBOResult[A]:

  def valueToDBOResult[A](a: A) : DBOResult[A] =
Applicative[DBOResult].pure(a)
def optionToDBOResult[A](o: Option[A]): DBOResult[A] =
OptionT(o.pure[DBOError])
def eitherToDBOResult[A](e: Either[String,A]): DBOResult[A] = {
val error: DBOError[A] = EitherT.fromEither[Task](e)
OptionT.liftF(error)
}
def taskToDBOResult[A](task: Task[A]): DBOResult[A] = {
val error: DBOError[A] = EitherT.liftF[Task,String,A](task)
OptionT.liftF(error)
}

都是些纯纯的帮助函数,一次定义了可以永久使用。下面就是一个具体应用的例子:

object session41 extends App {
type DBOError[A] = EitherT[Task,String,A]
type DBOResult[A] = OptionT[DBOError,A] def valueToDBOResult[A](a: A) : DBOResult[A] =
Applicative[DBOResult].pure(a)
def optionToDBOResult[A](o: Option[A]): DBOResult[A] =
OptionT(o.pure[DBOError])
def eitherToDBOResult[A](e: Either[String,A]): DBOResult[A] = {
val error: DBOError[A] = EitherT.fromEither[Task](e)
OptionT.liftF(error)
}
def taskToDBOResult[A](task: Task[A]): DBOResult[A] = {
val error: DBOError[A] = EitherT.liftF[Task,String,A](task)
OptionT.liftF(error)
} def task[T](t: T): Task[T] = Task.delay(t)
def add(a: Int, b: Int): Task[Int] = Task.delay(a + b) val calc: DBOResult[Int] = for {
a <- valueToDBOResult()
b <- optionToDBOResult(Some()) //None: Option[Int])
c <- eitherToDBOResult(Left[String,Int]("oh my good ..."))
d <- taskToDBOResult(add(b,c))
} yield d val sum: Task[Either[String,Option[Int]]] = calc.value.value import monix.execution.Scheduler.Implicits.global
import scala.util._
sum.runOnComplete {
case Success(s) => println(s"DBOResult sum=$s")
case Failure(exception) => println(exception.getMessage)
} } //DBOResult sum=Left(oh my good ...)

从这段代码的运算结果可以确定:复合Monad Transformer的效果是它的组成Monad效果的叠加。在上面这个例子里我们分别可以用None,Left来中断运算,产生break一样的效果。

深圳scala-meetup-20180902(3)- Using heterogeneous Monads in for-comprehension with Monad Transformer的更多相关文章

  1. PICE(1):Programming In Clustered Environment - 集群环境内编程模式

    首先声明:标题上的所谓编程模式是我个人考虑在集群环境下跨节点(jvm)的流程控制编程模式,纯粹按实际需要构想,没什么理论支持.在5月份的深圳scala meetup上我分享了有关集群环境下的编程模式思 ...

  2. 参加完Rocket MQ Meetup深圳站,回顾和想法

    最近一段时间才开始关注云栖社区的公众号,在两周前看到要在深圳科兴科学园办一场Rocket MQ的Meetup.因为从来没有参加过这种线下活动,而且对Rocket MQ比较感兴趣,所以就立即报名参加. ...

  3. 深圳scala-meetup-20180902(1)- Monadic 编程风格

    刚完成了9月份深圳scala-meetup,趁刮台风有空,把我在meetup里的分享在这里发表一下.我这次的分享主要分三个主题:“Monadic编程风格“.”Future vs Task and Re ...

  4. SDP(13): Scala.Future - far from completion,绝不能用来做甩手掌柜

    在前面几篇关于数据库引擎的讨论里很多的运算函数都返回了scala.Future类型的结果,因为我以为这样就可以很方便的实现了non-blocking效果.无论任何复杂的数据处理操作,只要把它们包在一个 ...

  5. Scala Option 从官方DOC解析

    Represents optional values. Instances of Option are either an instance of scala.Some or the object N ...

  6. monads-are-elephants(转)

    介绍monads有点像互联网时代的家庭手工业.我想 “为什么要反对传统?”,但这篇文章将以Scala对待monads的方式来描述. 有个古老的寓言,讲述了几个瞎子第一次摸到大象.一个抱着大象的腿说:“ ...

  7. 浅释Functor、Applicative与Monad

    引言 转入Scala一段时间以来,理解Functor.Applicative和Monad等概念,一直是我感到头疼的部分.虽然读过<Functors, Applicatives, And Mona ...

  8. Scalaz(45)- concurrency :Task-函数式多线程编程核心配件

    我们在上一节讨论了scalaz Future,我们说它是一个不完善的类型,最起码没有完整的异常处理机制,只能用在构建类库之类的内部环境.如果scalaz在Future类定义中增加异常处理工具的话,用户 ...

  9. Scalaz(41)- Free :IO Monad-Free特定版本的FP语法

    我们不断地重申FP强调代码无副作用,这样才能实现编程纯代码.像通过键盘显示器进行交流.读写文件.数据库等这些IO操作都会产生副作用.那么我们是不是为了实现纯代码而放弃IO操作呢?没有IO的程序就是一段 ...

随机推荐

  1. 为Firefox浏览器安装Firebug插件

    一.确保联网 二.打开Firefox 三.菜单:工具 -> 附加组件 显示附加组件管理器界面,点扩展 在搜索框输入firebug,搜,在搜索结果列表中找到Firebug项,安装 安装进度 安装完 ...

  2. K8s部署使用CFSSL创建证书

    证书的编码格式 PEM(Privacy Enhanced Mail),通常用于数字证书认证机构(Certificate Authorities,CA),扩展名为.pem, .crt, .cer, 和  ...

  3. 修改CKplayer.js 源码解决移动端浏览器全屏不能限制快进的问题

    原文地址:https://www.cnblogs.com/jying/p/9642445.html,转载请说明出处. 最近项目需要播放视频且限制未观看部分的快进功能,找了两款js插件ckplayer和 ...

  4. asp.net文件/大文件上传需要配置的项目整理

    HTTP 错误 404.13 - Not Found 请求筛选模块被配置为拒绝超过请求内容长度的请求. 最可能的原因: •Web 服务器上的请求筛选被配置为拒绝该请求,因为内容长度超过配置的值. 可尝 ...

  5. vue项目中使用axios上传图片等文件

    form表单提交图片会刷新页面,也可以时form绑定到一个隐藏的iframe上,可以实现无刷新提交数据. html代码: <input name="file" type=&q ...

  6. db2常见命令

    增加db2top命令的refresh间隔,默认值为2秒,下面的命令就可以每10秒刷新一次: $ db2top -i 10 -d sample 数据库本身太繁忙(dynamic SQL过多).建议增加 ...

  7. linux上部署Appach,让文件目录以网页列表形式访问

    效果: 1.首先,需要安装Apache httpd服务 yum install -y httpd 2.查看或者设置httpd主配文件 vim /etc/httpd/conf/htpd.conf 从中可 ...

  8. java程序的三种结构

    从结构化程序设计角度出发,程序有三种结构:   顺序结构: JAVA的基本结构就是顺序结构,除非特别指明,否则就按照顺序一句一句执行顺序结构是最简单的算法结构,语句与语句之间,框与框之间是按从上到下的 ...

  9. oday获取系统最高权限的代码

    import sys,sockettarget = sys.argv[1]shellcode = ("\x6a\x4f\x59\xd9\xee\xd9\x74\x24\xf4\x5b\x81 ...

  10. php面试题五之nginx如何调用php和php-fpm的作用和工作原理

    nginx如何调用php 采用nginx+php作为webserver的架构模式,在现如今运用相当广泛.然而第一步需要实现的是如何让nginx正确的调用php.由于nginx调用php并不是如同调用一 ...