设为首页 加入收藏

TOP

深圳scala-meetup-20180902(1)- Monadic 编程风格(一)
2019-08-15 00:11:32 】 浏览:151
Tags:深圳 scala-meetup-20180902 Monadic 编程 风格

  刚完成了9月份深圳scala-meetup,趁刮台风有空,把我在meetup里的分享在这里发表一下。我这次的分享主要分三个主题:“Monadic编程风格“、”Future vs Task and ReaderMonad应用方法“及”using heterogeneous monads in for-comprehension with MonadTransformer“。这篇想先介绍一下Monadic编程风格。

Monadic编程就是用Monad来编程,它的形式是:F[G],F是个Monad,然后G是具体的运算,G就是我们习惯的运算表达式如1+1、update('a','new content')等等,可能会产生副作用的,比如输入输出,更改数据等。形象点描述:如果我们把F[_]当作是一个管道,那么Monadic编程模式就像是在F这个管道里组装连接一些可能产生副作用的运算表达式。实际上真正产生运算结果的是管道内部的这些运算表达式。这是疯了吗?我们为什么不直接按序运算这些表达式来获取结果呢?我们先听听下面的分析:

看看下面这段程序:

行令编程模式(imperative programming) def au(t:T): T async update with result val t2 = au(t1) val t3 = au(t2) val t4 = au(t2 + t3)         t4 = ???

如果上面每一行指令都在不同的线程里运算,那么完成运算的顺序就是不确定的。最后t4的结果是不可预料的了。为了保证这个运算顺序,我们可能要使用锁,这又回到在OO编程里最棘手的问题:运行低效、死锁、难以理解跟踪等。基本上OO编程的多线程程序不但难以理解而且运算难以捉摸,结果难以预览,很难做的对。我们再看看Monadic编程:

monadic programming : program with monads val fp3 = F[p1] ? F[p1] ? F[p1] = F[p1+p2+p3] 1、延迟运算 :val res = fp3.run 2、按序运算 :flatMap{a => flatMap{b => flatMap{c =>… 

我们看到:所谓的Monadic编程就是在F[_]管道内运算式p1,p2,p3的连接。这样做可以达到延迟运算和按序运算两个主要目的。延迟运算可以让我们完成对所有运算表达式的组合再一次性进行完整的运算。按序运算可以保证运算是按照编程人员的意图进行的,这里的flatMap是一种函数链,运算得到a后再运算b,得到b后再继续运算c 。。。

下面是我们自创的一个F[_]结构Tube[A]和它的使用示范:

 case class Tube[A](run: A) { def map[B](f: A => B): Tube[B] = Tube(f(run)) def flatMap[B](f: A => Tube[B]): Tube[B] = f(run) } val value: Tube[Int] = Tube(10) def add(a: Int, b: Int): Tube[Int] = Tube(a+b) val f = for { a <- value b <- add(a , 3) c <- add(a,b) } yield c println(f) //Tube(23)
  println(f.run)      //23

首先,Tube[A]是个Monad,因为它支持map和flatMap。对任何Tube类型我们都可以用for-comprehension来组合运算式,最后run来获取运算结果。以上a,b,c都是中间结果,可以在for{...}中任意使用。

值得注意的是:Monadic操作与scala里集合的操作很相似,不同的是Monadic操作类型只包含一个内部元素,而集合包含了多个元素,如List(1,2,3)有3个元素。

实际上,简单的一个Tube结构好像没什么特别用处,说白了它连中途终止运算的功能都没有。scala库里现成的Monad中Option,Either都有特别的作用:Option可以在遇到None值时中断运算并立即返回None值。Either在遇到Left值时立即返回Left,如下:

  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
 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) //oh no ...

好了,下面我们就用一个形象点的例子来示范Monadic编程风格:这是一个模拟数据库操作的例子,我们用一个KVStore来模拟数据库:

  class KVStore[K,V] { private val s = new ConcurrentHashMap[K,V]() def create(k: K, v: V): Future[Boolean] = Future.successful(s.putIfAbsent(k,v) == null) def read(k: K): Future[Option[V]] = Future.successful(Option(s.get(k))) def update(k: K, v: V): Future[Unit] = Future.successful(s.put(k,v)) def delete(k: K): Future[Boolean] = Future.successful(s.remove(k) == null) }

对KVStore的操作函数都采用了Future作为结果类型,这样可以实现non-blocking操作。Future是个Monad(虽然它不是一种纯函数impure function, 这个我们后面再解释),所以我们可以用for-comprehension来编程,如下:

 type FoodName = String type Quantity = Int type FoodStore = KVStore[String,Int] def addFood(food: FoodName, qty: Quantity )(implicit fs: FoodStore): Future[Unit] = for { current <- fs.read(food) newQty = current.map(cq => cq + qty ).getOrElse(qty) _ <- fs.update(food, newQty) } yield () def takeFood(food: FoodName, qty: Quantity)(implicit fs: FoodStore): Future[Quantity] = for { current <- fs.read(food) instock = current.getOrElse(0) taken = M
首页 上一页 1 2 下一页 尾页 1/2/2
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇PICE(6):集群环境里多异类端点.. 下一篇深圳scala-meetup-20180902(2)-..

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目