长久以来,函数式编程模式都被认为是一种学术研究用或教学实验用的编程模式。直到近几年由于大数据和多核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. Fiddler (六) 最常用的快捷键

    使用QuickExec Fiddler2成了网页调试必备的工具,抓包看数据.Fiddler2自带命令行控制,并提供以下用法. Fiddler的快捷命令框让你快速的输入脚本命令. 键盘快捷键 按ALT+ ...

  2. “胡”说IC——菜鸟工程师完美进阶

    “胡”说IC——菜鸟工程师完美进阶(数十位行业精英故事分享,顶级猎头十多年来经验总结,对将入或初入IC电子业“菜鸟”职业发展.规划的解惑和点拨.) 胡运旺 编著   ISBN 978-7-121-22 ...

  3. Android笔记——我的Android课的开始

    android 最底层的是什么?  硬件 介于硬件与软件之间的一个交互,你猜猜需要什么? 软件的上面一层便是各种的类库 硬件与软件之间的交互,就是需要驱动的进行. 1.android系统架构 1.Li ...

  4. Android WebView 总结 —— Java和JavaScript交互

    交互如何实现 实现Java和js交互十分便捷.通常只需要以下几步. WebView开启JavaScript脚本执行 WebView设置供JavaScript调用的交互接口. 客户端和网页端编写调用对方 ...

  5. 每天一个linux命令(31): /etc/group文件详解

    Linux /etc/group文件与/etc/passwd和/etc/shadow文件都是有关于系统管理员对用户和用户组管理时相关的文件.linux /etc/group文件是有关于系统管理员对用户 ...

  6. inoic start projectname sidemenu报错 - Error: Cannot find module 'lodash._baseslice'

    inoic start projectname sidemenu报错 - Error: Cannot find module 'lodash._baseslice' 在公司的电脑上出现过这个错误,后来 ...

  7. 快速入门系列--MVC--06视图

    到了View的呈现板块,感觉ASP.NET MVC的学习也进入了尾声,还是比较开心的,毕竟也有了不小收获.这部分内容相对比较简单,因为之前还专门学习过如何结合HTML5与MVC框架.前文中提到过,Ac ...

  8. 引入CSS文件的@import与link的权重分析

    我很少在CSS用到@import这个标签,最近看到一句话“link方式的样式的权重 高于@import的权重”,感觉不太对,@import只是一个引入外部文件而已,怎么会有高于link的权重呢?于是我 ...

  9. OpenCascade BRep Format Description (2)

    OpenCascade BRep Format Description eryar@163.com 摘要Abstract:本文结合OpenCascade的BRep格式描述文档和源程序,对BRep格式进 ...

  10. 信息加密之信息摘要加密MD2、MD4、MD5

    对于用户数据的保密一直是各个互联网企业头疼的事,那如何防止用户的个人信息泄露呢?今天为大家介绍一种最简单的加密方式--信息摘要算法MD.它如何来保护用户的个人信息呢?其实很简单,当获得到用户的信息后, ...