在对上一次3月份的scala-meetup里我曾分享了关于Future在函数组合中的问题及如何用Monix.Task来替代。具体分析可以查阅这篇博文。在上篇示范里我们使用了Future来实现某种non-blocking数据库操作,现在可以用Task替换Future部分:

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

Task是一个真正的Monad,我们可以放心的用来实现函数组合:

  type FoodName = String
type Quantity = Int
type FoodStore = KVStore[String,Int] def addFood(food: FoodName, qty: Quantity)(implicit fs: FoodStore): Task[Quantity] = for {
current <- fs.read(food)
newQty = current.map(cq => cq + qty).getOrElse(qty)
_ <- fs.update(food,newQty)
} yield newQty def takeFood(food: FoodName, qty: Quantity)(implicit fs: FoodStore): Task[Quantity] = for {
current <- fs.read(food)
cq = current.getOrElse()
taken = Math.min(cq,qty)
left = cq - taken
_ <- if(left > ) fs.update(food,left) else fs.delete(food)
} yield taken def cookSauce(qty: Quantity)(get: (FoodName,Quantity) => Task[Quantity],
put: (FoodName,Quantity) => Task[Quantity]): Task[Quantity] = for {
tomato <- get("Tomato",qty)
vaggies <- get("Veggies",qty)
_ <- get("Galic",)
sauceQ = tomato/ + vaggies * /
_ <- put("Sauce",sauceQ)
} yield sauceQ def cookPasta(qty: Quantity)(get: (FoodName,Quantity) => Task[Quantity],
put: (FoodName,Quantity) => Task[Quantity]): Task[Quantity] = for {
pasta <- get("Pasta", qty)
sauce <- get("Sauce", qty)
_ <- get("Spice", )
portions = Math.min(pasta, sauce)
_ <- put("Meal", portions)
} yield portions

跟上次我们使用Future时的方式没有两样。值得研究的是如何获取Task运算结果,及如何更精确的控制Task运算如取消运行中的Task:

  implicit val refridge = new FoodStore

  val shopping: Task[Unit] = for {
_ <- addFood("Tomato",)
_ <- addFood("Veggies",)
_ <- addFood("Garlic", )
_ <- addFood("Spice", )
_ <- addFood("Pasta", )
} yield() val cooking: Task[Quantity] = for {
_ <- shopping
sauce <- cookSauce()(takeFood(_,_),addFood(_,_))
meals <- cookPasta()(takeFood(_,_),addFood(_,_))
} yield meals import scala.util._
import monix.execution.Scheduler.Implicits.global val cancellableCooking = Cooking.runOnComplete { result =>
result match {
case Success(meals) => println(s"we have $meals pasta meals for the day.")
case Failure(err) => println(s"cooking trouble: ${err.getMessage}")
}
} global.scheduleOnce( second) {
println(s"its taking too long, cancelling cooking ...")
cancellableCooking.cancel()
}

在上面例子里的addFood,takeFood函数中都有个fs:FoodStore参数。这样做可以使函数更加通用,可以对用不同方式实施的FoodStore进行操作。这里FoodStore就是函数的依赖,我们是通过函数参数来传递这个依赖的。重新组织一下代码使这种关系更明显:

  class Refridge {
def addFood(food: FoodName, qty: Quantity): FoodStore => Task[Quantity] = { foodStore =>
for {
current <- foodStore.read(food)
newQty = current.map(c => c + qty).getOrElse(qty)
_ <- foodStore.update(food, newQty)
} yield newQty
} def takeFood(food: FoodName, qty: Quantity): FoodStore => Task[Quantity] = { foodStore =>
for {
current <- foodStore.read(food)
cq = current.getOrElse()
taken = Math.min(cq, qty)
left = cq - taken
_ <- if (left > ) foodStore.update(food, left) else foodStore.delete(food)
} yield taken
} }

现在我们用一个函数类型的结果来代表依赖注入。这样做的好处是简化了函数主体,彻底把依赖与函数进行了分割,使用函数时不必考虑依赖。

scala的函数式组件库cats提供了一个Kleisli类型,reader monad就是从它推导出来的:

 final case class Kleisli[M[_], A, B](run: A => M[B]) { self =>
...
trait KleisliFunctions {
/**Construct a Kleisli from a Function1 */
def kleisli[M[_], A, B](f: A => M[B]): Kleisli[M, A, B] = Kleisli(f)

def >=>[C](k: Kleisli[M, B, C])(implicit b: Bind[M]): Kleisli[M, A, C] =
kleisli((a: A) => b.bind(this(a))(k.run))

Kleisli的用途就是进行函数的转换
// (A=>M[B]) >=> (B=>M[C]) >=> (C=>M[D]) = M[D]

实际上Kleisli就是ReaderT:

 type ReaderT[F[_], E, A] = Kleisli[F, E, A]
val ReaderT = Kleisli
val reader = ReaderT[F,B,A](A => F[B])
val readerTask = ReaderT[Task,B,A](A => Task[B])
val injection = ReaderT { foodStore => Task.delay { foodStore.takeFood } }
val food = injection.run(db) // run(kvs), run(dbConfig) …

这段代码里我们也针对上面的例子示范了ReaderT的用法。现在我们可以把例子改成下面这样:

  type FoodName = String
type Quantity = Int
type FoodStore = KVStore[String,Int] class Refridge {
def addFood(food: FoodName, qty: Quantity): ReaderT[Task,FoodStore,Quantity] = ReaderT{ foodStore =>
for {
current <- foodStore.read(food)
newQty = current.map(c => c + qty).getOrElse(qty)
_ <- foodStore.update(food, newQty)
} yield newQty
} def takeFood(food: FoodName, qty: Quantity): ReaderT[Task,FoodStore,Quantity] = ReaderT{ foodStore =>
for {
current <- foodStore.read(food)
cq = current.getOrElse()
taken = Math.min(cq, qty)
left = cq - taken
_ <- if (left > ) foodStore.update(food, left) else foodStore.delete(food)
} yield taken
} }

ReaderT[F[_],E,A]就是ReaderT[Task,FoodStore,Quantity]. FoodStore是注入的依赖,ReaderT.run返回Task:

  val cooking: ReaderT[Task,FoodStore,Quantity] = for {
_ <- shopping
sauce <- cooker.cookSauce()
pasta <- cooker.cookPasta()
} yield pasta import scala.concurrent.duration._
import scala.util._
import monix.execution.Scheduler.Implicits.global
val timedCooking = cooking.run(foodStore).timeoutTo( seconds, Task.raiseError( new RuntimeException(
"oh no, take too long to cook ...")))
val cancellableCooking = timedCooking.runOnComplete { result =>
result match {
case Success(meals) => println(s"we have $meals specials for the day.")
case Failure(exception) => println(s"kitchen problem! ${exception.getMessage}")
}
}
global.scheduleOnce( seconds) {
println("3 seconds passed,cancelling ...")
cancellableCooking.cancel()
}

我们知道cooking是个ReaderT,用run(foodStore)来注入依赖foodStore。那么如果我们还有一个kvStore或者jdbcDB,mongoDB可以直接用run(kvStore), run(jdbcDB), run(mongoDB) ... 返回的结果都是Task。

深圳scala-meetup-20180902(2)- Future vs Task and ReaderMonad依赖注入的更多相关文章

  1. dotnet core在Task中使用依赖注入的Service/EFContext

    C#:在Task中使用依赖注入的Service/EFContext dotnet core时代,依赖注入基本已经成为标配了,这就不多说了. 前几天在做某个功能的时候遇到在Task中使用EF DbCon ...

  2. Scala依赖注入

    控制反转(Inversion of Control,简称IoC),是面向对象编程中的一种设计原则,可以用来降低计算机代码之间的耦合程度.其中最常见的方式叫做依赖注入(Dependency Inject ...

  3. asyncio模块中的Future和Task

      task是可以理解为单个coroutine,经过ensure_future方法处理而形成,而众多task所组成的集合经过asyncio.gather处理而形成一个future. 再不精确的粗略的说 ...

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

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

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

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

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

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

  7. PYTHON ASYNCIO: FUTURE, TASK AND THE EVENT LOOP

    from :http://masnun.com/2015/11/20/python-asyncio-future-task-and-the-event-loop.html Event Loop On ...

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

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

  9. Scalaz(44)- concurrency :scalaz Future,尚不完整的多线程类型

    scala已经配备了自身的Future类.我们先举个例子来了解scala Future的具体操作: import scala.concurrent._ import ExecutionContext. ...

随机推荐

  1. JQ attr prop 区别

    解决方法:使用prop属性代替attr属性 一.Attr除 checked, selected, 或 disabled状态属性外,其余的属性均可用attr()设置和修改.$("img&quo ...

  2. [译]与TensorFlow的第一次接触(三)之聚类

    转自 [译]与TensorFlow的第一次接触(三)之聚类 2016.08.09 16:58* 字数 4316 阅读 7916评论 5喜欢 18 前一章节中介绍的线性回归是一种监督学习算法,我们使用数 ...

  3. python 装饰器的理解以及类装饰器

    python装饰器的作用就是在不改变原有函数结构的情况下给该函数增加一个新功能,就是不入侵到原来函数里面修改代码给其增加一个新功能 先看代码 def out(fn): def inner(*args, ...

  4. week0713.5 newspaper 安装问题

    然后爆红说nltk==2.0.5 太老了 所以我们 把包下载下来将requirements文件中nltk==2.0.5改成3.3.0 然后 将包打包 然后安装这个包就OK 下载newpaper wge ...

  5. 大数据入门到精通9-真正得wordcount

    本章节实现一个真正得wordcount 得spark程序. 一.从本地获得一个数据集 val speechRdd= sc.parallelize(scala.io.Source.fromFile(&q ...

  6. Java学习笔记(二十二):打包程序

    加入一个程序测试完毕,我们就可以将它打包,就可以放到服务器上运行了 找到左下角的终端 点击 输入命令: mvnw clean package -DskipTests=true clean:清除以前生成 ...

  7. Django 学生信息 添加 功能 遇到的问题.

    1  添加 班级信息时的问题 (grade为外键) 原因是 grade 必需接收 一个 实例, 而我交是一个 str字符串, if request.method == 'POST': data = { ...

  8. FortiGate外网IPSec链路及运维专线链路到个别网段不通

    1.现状: 如图,用户网段有192.168.50.0/24.192.168.51.0/24和192.168.52.0/24.192.168.53.0/24.在防火墙上有静态路由到运维专线的10.160 ...

  9. 大数据OLAP引擎对比

    Presto:内存计算,mpp架构   PB级别数据 presto适合pb级的海量数据查询分析,不是说把pb的数据放进内存,比如一张pb表,查询count,vag这种有个特点,虽然数据很多,但是最终的 ...

  10. IntelliJ IDEA常用快捷键(Mac)

    Mac 键盘符号和修饰键说明 ⌘ ——> Command ⇧ ——> Shift ⌥ ——> Option ⌃ ——> Control ↩︎ ——> Return/Ent ...