长久以来,函数式编程模式都被认为是一种学术研究用或教学实验用的编程模式。直到近几年由于大数据和多核CPU的兴起造成了函数式编程模式在一些实际大型应用中的出现,这才逐渐改变了人们对函数式编程无用论的观点。通过一段时间对函数式编程方法的学习,我们了解到Free Monad的算式/算法关注分离(separation of concern)可以是一种很实用的函数式编程模式。用Free Monad编写的程序容易理解并具备良好的可维护性。scalaz-stream的流程控制和多线程运算模式可以实现程序的安全并行运算。把Free Monad和scalaz-stream有机结合起来可以形成一种新的编程模式来支持函数式多线程编程来编制具备安全性、易扩展、易维护的并行运算程序。我们先从一个简单的Free Monad程序开始:

 import scalaz._
import Scalaz._
import scalaz.concurrent._
import scalaz.stream._
import scala.language.higherKinds
import scala.language.implicitConversions
object freeStream {
//1. 定义语句
object DSLs {
sealed trait Interact[A]
case class Ask(q: String) extends Interact[String]
case class Tell(m: String) extends Interact[Unit]
//2. Free升格
implicit def interactToFree[A](ia: Interact[A]) = Free.liftF(ia)
}
//3. 程序逻辑/算式
object PRGs {
import DSLs._
val prgGetName: Free[Interact,Unit] = for {
first <- Ask("What's your first name?")
last <- Ask("What's your last name?")
_ <- Tell(s"Hello $first $last")
} yield ()
}
//4. 实现方式/算式
object IMPs {
import DSLs._
object InteractConsole extends (Interact ~> Id) {
def apply[A](ia: Interact[A]): Id[A] = ia match {
case Ask(q) => {println(q); Console.readLine}
case Tell(m) => println(m)
}
}
}

在这个程序里我们按照一个固定的框架步骤来实现“定义语句”、“升格Free”、“功能描述”及“实现方式”。这里特别需要注意的是所谓的算式/算法关注分离,即“功能描述”和“实现方式”是互不关联的。这样我们可以提供不同版本的实现方式来进行测试、环境转换等工作。Free Monad的具体运算方式如下:

 //5. 运算/Run
import DSLs._,PRGs._,IMPs._
prgGetName.foldMapRec(InteractConsole)

运算结果返回A:对于prgGetName来说就是Unit。不过如果直接运行foldMapRec有可能会产生副作用(siede effect)。这样不符合纯代码要求,无法实现这个程序与其它程序的函数组合。我们需要把这段可能产生副作用的代码放到Task里:

 val taskGetName = Task.delay { prgGetName.foldMapRec(InteractConsole)}
//> taskGetName : scalaz.concurrent.Task[scalaz.Scalaz.Id[Unit]] = scalaz.concurrent.Task@282ba1e

这样我们就获得了一个异线程的延迟运算。我们可以放心地用这个taskGetName进行函数组合。把这个Free Monad程序转换成scalaz-stream的Process也很容易:

 val prcGetName = Process.eval(taskGetName)  //> prcGetName  : scalaz.stream.Process[scalaz.concurrent.Task,scalaz.Scalaz.Id[Unit]] = Await(scalaz.concurrent.Task@282ba1e,<function1,<function1>)

我们用Process.eval直接把它转换成Process[Task,Unit]类型。下面我们用scalaz-stream的运算方式来运算这个Free Monad程序:

 object FreeInteract extends App {
import DSLs._,PRGs._,IMPs._
val taskGetName = Task.delay { prgGetName.foldMapRec(InteractConsole)}
val prcGetName = Process.eval(taskGetName)
prcGetName.run.run
}

运算结果如下:

 What's your first name?
tiger
What's your last name?
chan
Hello, tiger chan!

虽然这个例子看起来很简单,但其中代表的意义却不小:我们潜移默化地实现了函数式多线程编程了。

如果我们需要Free Monad程序返回运算结果的话就调整一下功能描述(算式):

   val prgGetUserID = for {
uid <- ask("Enter User ID:")
} yield uid

再运算一下:

 object FreeInteract extends App {
import DSLs._,PRGs._,IMPs._
val taskGetName = Task.delay { prgGetName.foldMapRec(InteractConsole)}
val prcGetName = Process.eval(taskGetName)
//prcGetName.run.run
Process.eval(Task.delay{prgGetUserID.foldMapRec(InteractConsole)}).runLog.run.map(println)
...
Enter User ID:
tiger123
tiger123

用纯代码方式echo输入:

   pUserID.evalMap { uid => Task.delay {prgEchoInput(uid).foldMapRec(InteractConsole)} }.run.run
...
Enter User ID:
user234
user234

也可以把结果发送到一个Sink来显示:

val outSink: Sink[Task,String] = Process.constant{x =>Task.delay{prgEchoInput(x).foldMapRec(InteractConsole)}}
(pUserID to outSink).run.run
...
Enter User ID:
jonathon
jonathon

我们试着再加一个Free程序功能:验证用户编号

   sealed trait Login[A]
case class CheckID(id: String) extends Login[Boolean]
...
def prgCheckID(id: String) = for {
b <- Free.liftF(CheckID(id))
} yield b
...
object UserLogin extends (Login ~> Id) {
def apply[A](la: Login[A]): Id[A] = la match {
case CheckID(id) => if (id === "tiger123") true else false
}
}

stream流程是:先读取用户编号然后验证,跟着在Sink输出结果:

  def fCheckID: String => Task[String] = id => Task.delay { prgCheckID(id).foldMapRec(UserLogin) }.map(_.toString)
val chCheckID = channel.lift(fCheckID)
((pUserID through chCheckID) to outSink).run.run
... Enter User ID:
tiger123
true
...
Enter User ID:
johnny234
false

不错!Free Monad和scalar-stream可以很好的集成在一起。
我把这节讨论的示范源代码提供给大家:

 import scalaz._
import Scalaz._
import scalaz.concurrent._
import scalaz.stream._
object DSLs {
sealed trait Interact[A]
case class Ask(q: String) extends Interact[String]
case class Tell(m: String) extends Interact[Unit]
object Interact {
def ask(q: String): Free[Interact, String] = Free.liftF(Ask(q))
def tell(m: String): Free[Interact, Unit] = Free.liftF(Tell(m))
}
sealed trait Login[A]
case class CheckID(id: String) extends Login[Boolean]
}
object PRGs {
import DSLs._
import Interact._ val prgGetName = for {
first <- ask("What's your first name?")
last <- ask("What's your last name?")
_ <- tell(s"Hello, $first $last!")
} yield() val prgGetUserID = for {
uid <- ask("Enter User ID:")
} yield uid def prgEchoInput(m: String) = tell(m) def prgCheckID(id: String) = for {
b <- Free.liftF(CheckID(id))
} yield b }
object IMPs {
import DSLs._
object InteractConsole extends (Interact ~> Id) {
def apply[A](ia: Interact[A]): Id[A] = ia match {
case Ask(q) => { println(q); readLine }
case Tell(m) => println(m)
}
}
object UserLogin extends (Login ~> Id) {
def apply[A](la: Login[A]): Id[A] = la match {
case CheckID(id) => if (id === "tiger123") true else false
}
}
} object FreeInteract extends App {
import DSLs._,PRGs._,IMPs._
val taskGetName = Task.delay { prgGetName.foldMapRec(InteractConsole)}
val prcGetName = Process.eval(taskGetName)
//prcGetName.run.run
val pUserID= Process.eval(Task.delay{prgGetUserID.foldMapRec(InteractConsole)})
//pUserID.evalMap { uid => Task.delay {prgEchoInput(uid).foldMapRec(InteractConsole)} }.run.run
val outSink: Sink[Task,String] = Process.constant { x => Task.delay {prgEchoInput(x).foldMapRec(InteractConsole) } }
//(pUserID to outSink).run.run
def fCheckID: String => Task[String] = id => Task.delay { prgCheckID(id).foldMapRec(UserLogin) }.map(_.toString)
val chCheckID = channel.lift(fCheckID)
((pUserID through chCheckID) to outSink).run.run

Scalaz(54)- scalaz-stream: 函数式多线程编程模式-Free Streaming Programming Model的更多相关文章

  1. Java多线程编程模式实战指南(三):Two-phase Termination模式

    停止线程是一个目标简单而实现却不那么简单的任务.首先,Java没有提供直接的API用于停止线程.此外,停止线程时还有一些额外的细节需要考虑,如待停止的线程处于阻塞(等待锁)或者等待状态(等待其它线程) ...

  2. Java多线程编程模式实战指南(三):Two-phase Termination模式--转载

    本文由本人首次发布在infoq中文站上:http://www.infoq.com/cn/articles/java-multithreaded-programming-mode-two-phase-t ...

  3. .NET “底层”异步编程模式——异步编程模型(Asynchronous Programming Model,APM)

    本文内容 异步编程类型 异步编程模型(APM) 参考资料 首先澄清,异步编程模式(Asynchronous Programming Patterns)与异步编程模型(Asynchronous Prog ...

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

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

  5. Java多线程编程模式实战指南:Active Object模式(上)

    Active Object模式简介 Active Object模式是一种异步编程模式.它通过对方法的调用与方法的执行进行解耦来提高并发性.若以任务的概念来说,Active Object模式的核心则是它 ...

  6. Java多线程编程模式实战指南(二):Immutable Object模式

    多线程共享变量的情况下,为了保证数据一致性,往往需要对这些变量的访问进行加锁.而锁本身又会带来一些问题和开销.Immutable Object模式使得我们可以在不使用锁的情况下,既保证共享变量访问的线 ...

  7. Java多线程编程模式实战指南一:Active Object模式(上)

    Active Object模式简介 Active Object模式是一种异步编程模式.它通过对方法的调用与方法的执行进行解耦来提高并发性.若以任务的概念来说,Active Object模式的核心则是它 ...

  8. Java多线程编程模式实战指南之Promise模式

    Promise模式简介(转) Promise模式是一种异步编程模式 .它使得我们可以先开始一个任务的执行,并得到一个用于获取该任务执行结果的凭据对象,而不必等待该任务执行完毕就可以继续执行其他操作.等 ...

  9. java多线程编程模式

    前言 区别于java设计模式,下面介绍的是在多线程场景下,如何设计出合理的思路. 不可变对象模式 场景 1. 对象的变化频率不高 每一次变化就是一次深拷贝,会影响cpu以及gc,如果频繁操作会影响性能 ...

随机推荐

  1. MyBatis学习总结(七)——Mybatis缓存

    一.MyBatis缓存介绍 正如大多数持久层框架一样,MyBatis 同样提供了一级缓存和二级缓存的支持 一级缓存: 基于PerpetualCache 的 HashMap本地缓存,其存储作用域为 Se ...

  2. Atitit 图像处理 公共模块 矩阵扫描器

    Atitit 图像处理 公共模块 矩阵扫描器 1.1. 调用说明对矩阵像素遍历处理调用1 2. 矩阵扫描器主题结构1 2.1. 主要说明 从像素点开始填充矩阵1 2.2. 得到模板中心点所对应的图像坐 ...

  3. iOS开发——高级技术精选OC篇&Runtime之字典转模型实战

    Runtime之字典转模型实战 如果您还不知道什么是runtime,那么请先看看这几篇文章: http://www.cnblogs.com/iCocos/p/4734687.html http://w ...

  4. js 优化

    一.for循环的优化 <!doctype html> <html lang="en"> <head> <meta charset=&quo ...

  5. 神马是 NaN,它的类型是神马?怎么测试一个值是否等于 NaN?

    NaN 是 Not a Number 的缩写,JavaScript 的一种特殊数值,其类型是 Number,可以通过 isNaN(param) 来判断一个值是否是 NaN: console.log(i ...

  6. Unity5.x在WP8.1中无法使用Reflection API的解决方法

    下班前随便写点,虽然花了不少时间但是最终得到的解决方法还是比较简单的. 第一种方法:使用WinRTLegacy.dll中的类.这个dll在生成的WP project中是自带的无需在unity工程中添加 ...

  7. 找到SQL Server数据库历史增长信息

        很多时候,在我们规划SQL Server数据库的空间,或向存储方面要空间时,都需要估算所需申请数据库空间的大小,估计未来最简单的办法就是看过去的趋势,这通常也是最合理的方式.     通常来讲 ...

  8. 【WP8.1开发】基于应用的联系人存储

    上一篇文章所吹的牛是访问系统(手机)上的联系人,当然那只是读不能改,这是自然的,要是让你能随便修改用户的联系人信息的话,那后果很严重,有些恶意开发者就有可能把”你的户口改成猪“. 但是,API也允许应 ...

  9. javascript类型系统——数组array

    × 目录 [1]创建 [2]本质 [3]稀疏[4]长度[5]遍历[6]类数组 前面的话 除了对象之外,数组Array类型可能是javascript中最常用的类型了.而且,javascript中的数组与 ...

  10. Java性能优化

    作者:禅楼望月(http://www.cnblogs.com/yaoyinglong) 注:里面的测试结果会因电脑配置的不同而有所差异!!! 1. 为一些集合定义初始化大小 List.Set.Map都 ...