刚完成了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]
、延迟运算 :val res = fp3.run
、按序运算 :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()
def add(a: Int, b: Int): Tube[Int] = Tube(a+b) val f = for {
a <- value
b <- add(a , )
c <- add(a,b)
} yield c println(f) //Tube(23)
println(f.run) //

首先,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()
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 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) //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()
taken = Math.min(instock,qty)
left = instock - taken
_ <- if (left > ) fs.update(food,left) else fs.delete(food)
} yield taken def cookSauce(qty: Quantity)(get: (FoodName,Quantity) => Future[Quantity],
put:(FoodName,Quantity) => Future[Unit]): Future[Quantity] = for {
tomato <- get("Tomato",qty)
veggie <- get("Veggie",qty)
garlic <- get("Garlic", qty * )
sauceQ = tomato / + veggie * /
_ <- put("Sauce",sauceQ)
} yield sauceQ def cookMeals(qty: Quantity)(get: (FoodName,Quantity) => Future[Quantity],
put: (FoodName,Quantity) => Future[Unit]): Future[Quantity] =
for {
pasta <- get("Pasta", qty)
sauce <- get("Sauce", qty)
_ <- get("Spice",) meals = Math.min(pasta,sauce)
_ <- put("Meal", meals) } yield meals

上面几个操作函数都是Future类型的,具体的操作都包含在for{...}里。我们看到:在for{...}里可以产生中间结果、也可以直接写运算表达式、也可以使用这些中间运算结果。for{...}里的情景就像正常的行令式编程。然后我们又对这些操作函数进行组合:

   implicit val refrigerator = new FoodStore

   val shopping: Future[Unit] = for {
_ <- addFood("Tomato", )
_ <- addFood("Veggie", )
_ <- addFood("Garlic", )
_ <- addFood("Spice", )
_ <- addFood("Pasta", )
} yield () val cooking: Future[Quantity] = for {
_ <- shopping
sauce <- cookSauce()(takeFood(_,_),addFood(_,_))
meals <- cookMeals()(takeFood(_,_),addFood(_,_))
} yield (meals) val todaysMeals = Await.result(cooking, seconds) println(s"we have $todaysMeals pasta meals for the day.")

最后组合成这个cooking monad, 然后一次性Await.result(cooking...)获取最终结果。通过上面这个例子我们可以得到这么一种对Monadic编程风格的感觉,就是:用for-comprehension来组合,组合、再组合,然后run(Await.result)获取结果。

深圳scala-meetup-20180902(1)- Monadic 编程风格的更多相关文章

  1. C++服务器开发之基于对象的编程风格

    Thread.h #ifndef _THREAD_H_ #define _THREAD_H_ #include <pthread.h> #include <boost/functio ...

  2. ES6深入学习记录(三)编程风格

    今天学习阮一峰ES6编程风格,其中探讨了如何将ES6的新语法,运用到编码实践之中,与传统的JavaScript语法结合在一起,写出合理的.易于阅读和维护的代码. 1.块级作用域 (1)let 取代 v ...

  3. geotrellis使用(六)Scala并发(并行)编程

    本文主要讲解Scala的并发(并行)编程,那么为什么题目概称geotrellis使用(六)呢,主要因为本系列讲解如何使用Geotrellis,具体前几篇博文已经介绍过了.我觉得干任何一件事情基础很重要 ...

  4. 个人c语言编程风格总结

    总结一下我个人的编程风格及这样做的原因吧,其实是为了给实验室写一个统一的C语言编程规范才写的.首先声明,我下面提到的编程规范,是自己给自己定的,不是c语言里面规定的. 一件事情,做成和做好中间可能隔了 ...

  5. Google Java编程风格指南

    出处:http://hawstein.com/posts/google-java-style.html 声明:本文采用以下协议进行授权: 自由转载-非商用-非衍生-保持署名|Creative Comm ...

  6. 编程风格(Coding Style)要求

    编程风格(Coding Style)要求2.1.1 文件(1) 每个模块(module)一般应存在于单独的源文件中,通常源文件名与所包含模块名相同.(2) 每个设计文件开头应包含如下注释内容:? 年份 ...

  7. Javascript编程风格

    Douglas Crockford是Javascript权威,Json格式就是他的发明. 去年11月他有一个演讲(Youtube),谈到了好的Javascript编程风格是什么.我非常推荐这个演讲,它 ...

  8. Google Java编程风格指南中文版

    作者:Hawstein出处:http://hawstein.com/posts/google-java-style.html声明:本文采用以下协议进行授权: 自由转载-非商用-非衍生-保持署名|Cre ...

  9. 《C#编程风格》还记得多少

    开始实习之后,才发现自己是多么地菜.还有好多东西还要去学习. 公司很好,还可以帮你买书.有一天随口问了一下上司D,代码规范上面有什么要求.然后D在Amazon上面找到了这本书<C#编程风格(Th ...

随机推荐

  1. vmware 里MAC 鼠标能移动 无法单击

    vmware 里MAC  鼠标能移动 无法单击 移动有效果,能看到鼠标移动的光标,鼠标放到mac的图标上还有提示,就是无法单击. 键盘正常. 重启mac,重启vmware 20次好了,2小时.

  2. 设置 VS 工程目录不保存 sdf / VC.db 文件和 Ipch 文件夹

    使用 Visual Studio 建立 C++ 解决方案时,会生成 SolutionName.sdf(Visual Studio 2015 Update 2 后改为 project_name.VC.d ...

  3. HTML - input(转)

    自: http://www.runoob.com 标签定义及使用说明 <input> 标签规定了用户可以在其中输入数据的输入字段. <input> 元素在 <form&g ...

  4. 记一次vcsa6修复过程

    一. 某天发现一台vmware  vCenter Server Appliance services 6偶尔能登陆了,但极不稳定,连shell都偶尔能进...... 然后利用各种手段想方设法进到she ...

  5. xtrabackup备份还原MySQL数据库

    mysqldump 备份鉴于其自身的某些特性(锁表,本质上备份出来insert脚本或者文本,不支持差异备份),不太适合对实时性要求比较高的情况Xtrabackup可以解决mysqldump存在的上述的 ...

  6. cadence 17.2 安装破解

    安装包都在gaobo百度云/工具/开发工具  或者    下载链接 进去pcb edit 可能会提示 licese什么的,忽略就可以了.

  7. win10系统goole浏览器安装postMan插件

    1. 首先是下载PostMan工具,可以通过谷歌插件网站查询下载postman插件工具.解压文件 2. 解压压缩包 3. 修改_metadata文件重命名为metadata文件,保存待用.修改后为: ...

  8. SpringCloud详解

    来源于纯洁的微笑 http://www.ityouknow.com/spring-cloud.html

  9. CORSFilter 跨域资源访问

    CORS 定义 Cross-Origin Resource Sharing(CORS)跨来源资源共享是一份浏览器技术的规范,提供了 Web 服务从不同域传来沙盒脚本的方法,以避开浏览器的同源策略,是 ...

  10. 【转】Appium 优化版

    Appium 开源分享优化版 之前分享过PageObject+Python+Appium 本版本是对上次版本较大改版,主要解决了: 失败重连一次(默认一次)可配置多次 基于appium1.7.1 ui ...