深圳scala-meetup-20180902(1)- 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 = Math.min(instock,qty)
left = instock - taken
_ <- if (left > 0) 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 * 3)
sauceQ = tomato / 2 + veggie * 3 / 2
_ <- 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",10)
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", 10)
_ <- addFood("Veggie", 15)
_ <- addFood("Garlic", 42)
_ <- addFood("Spice", 100)
_ <- addFood("Pasta", 6)
} yield ()
val cooking: Future[Quantity] = for {
_ <- shopping
sauce <- cookSauce(10)(takeFood(_,_),addFood(_,_))
meals <- cookMeals(10)(takeFood(_,_),addFood(_,_))
} yield (meals)
val todaysMeals = Await.result(cooking,3 seconds)
println(s"we have $todaysMeals pasta meals for the day.")
最后组合成这个cooking monad, 然后一次性Await.result(cooking...)获取最终结果。通过上面这个例子我们可以得到这么一种对Monadic编程风格的感觉,就是:用for-comprehension来组合,组合、再组合,然后run(Await.result)获取结果。
- 使用SpringBoot开发REST服务
- CSS3 -webkit-filter 滤镜
- Docker+Jenkins持续集成环境(3)集成PMD、FindBugs、Checkstyle静态代码检查工具并邮件发送检查结果
- Javascript中的Label语句
- 从编辑距离、BK树到文本纠错
- iframe基本知识及iframe版本Tab切换
- switch语句以及与if的比较
- kgtemp文件转mp3工具
- AJAX原理与步骤
- 【编程基础】C语言FILE结构体以及缓冲区深入探讨
- 获取标签的那些事 之 动态集合
- 一起读源码之zookeeper(1) -- 启动分析
- R语言与机器学习(分类算法)决策树算法
- HTML5视音频-解决全屏下出现的控制栏
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 高性能MySQL学习笔记
- 【Netty之旅四】你一定看得懂的Netty客户端启动源码分析!
- dubbo(二)动态编译compiler
- HttpClient工具类
- java面试知识要点汇总(线程和锁)
- 「二八法则」的数据可视化:用帕累托图进行数据分析
- dubbo(三)服务运行容器Container
- python实现抓取web和xcx数据推送到wx和邮件
- 如何快速识别项目水平?
- 亲测有效 | OpenVINO支持ONNX格式文件直接读取了
- 【自动化测试】【Jest-Selenium】(01)—— Jest 入门
- 设计模式:原型模式
- 后端逆袭,一份不可多得的PHP学习指南
- 走进Java接口测试之工具类库 Hutool
- Go开源游戏服务器框架——Pitaya