从上面多篇的讨论中我们了解到scalaz-stream代表一串连续无穷的数据或者程序。对这个数据流的处理过程就是一个状态机器(state machine)的状态转变过程。这种模式与我们通常遇到的程序流程很相似:通过程序状态的变化来推进程序进展。传统OOP式编程可能是通过一些全局变量来记录当前程序状态,而FP则是通过函数组合来实现状态转变的。这个FP模式讲起来有些模糊和抽象,但实际上通过我们前面长时间对FP编程的学习了解到FP编程讲究避免使用任何局部中间变量,更不用说全局变量了。FP程序的数据A是包嵌在算法F[A]内的。FP编程模式提供了一整套全新的数据更新方法来实现对F[A]中数据A的操作。对许多编程人员来讲,FP的这种编程方式会显得很别扭、不容易掌握。如果我们仔细观察分析,会发觉scalaz-stream就是一种很好的FP编程工具:它的数据也是不可变的(immutable),并且是包嵌在高阶类型结构里的,是通过Process状态转变来标示数据处理过程进展的。scalaz-stream的数据处理是有序流程,这样可以使我们更容易分析理解程序的运算过程,它的三个大环节包括:数据源(source),数据传换(transducer)及数据终点(Sink/Channel)可以很形象地描绘一个程序运算的全过程。scalaz-stream在运算过程中的并行运算方式(parallel computaion)、安全资源使用(resource safety)和异常处理能力(exception handling)是实现泛函多线程编程最好的支持。我们先来看看scalaz-stream里的一个典型函数:

/**
* Await the given `F` request and use its result.
* If you need to specify fallback, use `awaitOr`
*/
def await[F[_], A, O](req: F[A])(rcv: A => Process[F, O]): Process[F, O] =
awaitOr(req)(Halt.apply)(rcv)
/**
* Await a request, and if it fails, use `fb` to determine the next state.
* Otherwise, use `rcv` to determine the next state.
*/
def awaitOr[F[_], A, O](req: F[A])(fb: EarlyCause => Process[F, O])(rcv: A => Process[F, O]): Process[F, O] =
Await(req,(r: EarlyCause \/ A) => Trampoline.delay(Try(r.fold(fb,rcv))))

这个await函数可以说是一个代表完整程序流程的典范。注意,awaitOr里的Await是个数据结构。这样我们在递归运算await时可以避免StackOverflowError的发生。req: F[A]代表与外界交互的一个运算,如从外部获取输入、函数rcv对这个req产生的运算结果进行处理并设定程序新的状态。

 import scalaz.stream._
import scalaz.concurrent._
object streamApps {
import Process._
def getInput: Task[Int] = Task.delay { } //> getInput: => scalaz.concurrent.Task[Int]
val prg = await(getInput)(i => emit(i * )) //> prg : scalaz.stream.Process[scalaz.concurrent.Task,Int] = Await(scalaz.concurrent.Task@4973813a,<function1>,<function1>)
prg.runLog.run //> res0: Vector[Int] = Vector(9)
}

这是一个一步计算程序。我们可以再加一步:

  val add10 = await1[Int].flatMap{i => emit(i + )}
//> add10 : scalaz.stream.Process[[x]scalaz.stream.Process.Env[Int,Any]#Is[x],Int] = Await(Left,<function1>,<function1>)
val prg1 = await(getInput)(i => emit(i * ) |> add10)
//> prg1 : scalaz.stream.Process[scalaz.concurrent.Task,Int] = Await(scalaz.concurrent.Task@6737fd8f,<function1>,<function1>)
prg1.runLog.run //> res0: Vector[Int] = Vector(19)

add10是新增的一个运算步骤,是个transducer所以调用了Process1的函数await1,并用pipe(|>)来连接。实际上我们可以用组合方式(compose)把add10和prg组合起来:

 val prg3 = prg |> add10                         //> prg3  : scalaz.stream.Process[scalaz.concurrent.Task,Int] = Append(Halt(End) ,Vector(<function1>))
prg3.runLog.run //> res1: Vector[Int] = Vector(19)

我们同样可以增加一步输出运算:

  val outResult: Sink[Task,Int] = sink.lift { i => Task.delay{println(s"the result is: $i")}}
//> outResult : scalaz.stream.Sink[scalaz.concurrent.Task,Int] = Append(Emit(Vector(<function1>)),Vector(<function1>))
val prg4 = prg1 to outResult //> prg4 : scalaz.stream.Process[[x]scalaz.concurrent.Task[x],Unit] = Append(Halt(End),Vector(<function1>, <function1>))
prg4.run.run //> the result is: 19

scalaz-stream的输出类型是Sink,我们用to来连接。那么如果需要不断重复运算呢:

 import scalaz._
import Scalaz._
import scalaz.concurrent._
import scalaz.stream._
import Process._
object streamAppsDemo extends App {
def putLine(line: String) = Task.delay { println(line) }
def getLine = Task.delay { Console.readLine }
val readL = putLine("Enter:>").flatMap {_ => getLine}
val readLines = repeatEval(readL)
val echoLine = readLines.flatMap {line => eval(putLine(line))}
echoLine.run.run
}

这是一个无穷运算程序:不停地把键盘输入回响到显示器上。下面是一些测试结果:

 Enter:>
hello world!
hello world!
Enter:>
how are you?
how are you?
Enter:>

当然,我们也可以把上面的程序表达的更形象些:

   val outLine: Sink[Task,String] = constant(putLine _).toSource
val echoInput: Process[Task,Unit] = readLines to outLine
//echoLine.run.run
echoInput.run.run

用to Sink来表述可能更形象。这个程序没有任何控制:甚至无法有意识地退出。我们试着加一些控制机制:

   def lines: Process[Task,String] = {
def go(line: String): Process[Task,String] =
line.toUpperCase match {
case "QUIT" => halt
case _ => emit(line) ++ await(readL)(go)
}
await(readL)(go)
} val prg = lines to outLine
prg.run.run

在rcv函数里检查输入是否quit,如果是就halt,否则重复运算await。现在可以控制终止程序了。

下面再示范一下异常处理机制:看看能不能有效的捕捉到运行时的错误:

   def mul(i: Int) = await1[String].flatMap { s => emit((s.toDouble * i).toString) }.repeat
val prg = (lines |> mul()) to outLine
prg.run.run

加了个transducer mul(5),如果输入是可转变为数字类型的就乘5否者会异常退出。下面是一些测试场景:

 Enter:>

 25.0
Enter:> 30.0
Enter:>
six
Exception in thread "main" java.lang.NumberFormatException: For input string: "six"
at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:)

我们可以用onFailure来捕捉任何错误:

   def mul(i: Int) = await1[String].flatMap { s => emit((s.toDouble * i).toString) }.repeat
//val prg = (lines |> mul(5)) to outLine
val prg = (lines |> mul()).onFailure { e => emit("invalid input!!!") } to outLine
prg.run.run

现在运算结果变成了下面这样:

 Enter:>

 25.0
Enter:> 30.0
Enter:>
six
invalid input!!!

证明我们捕捉并处理了错误。一个完整安全的程序还必须具备自动事后清理的功能。这项可以通过onComplete来实现:

   def mul(i: Int) = await1[String].flatMap { s => emit((s.toDouble * i).toString) }.repeat
//val prg = (lines |> mul(5)) to outLine
val prg = (lines |> mul()).onFailure { e => emit("invalid input!!!") }
val prg1 = prg.onComplete{ Process.eval(Task.delay {println("end of program"); ""}) } to outLine
prg1.run.run

测试结果如下:

 Enter:>

 25.0
Enter:> 30.0
Enter:>
six
invalid input!!!
end of program

再有一个值得探讨的就是这些程序的组合集成。scalaz-stream就是存粹的泛函类型,那么基于scalaz-stream的程序就自然具备组合的能力了。我们可以用两个独立的程序来示范Process程序组合:

 import scalaz._
import Scalaz._
import scalaz.concurrent._
import scalaz.stream._
import Process._
object prgStream extends App {
def prompt(prmpt: String) = Task.delay { print(prmpt) }
def putLine(line: String) = Task.delay { println(line) }
def getLine = Task.delay { Console.readLine }
val readLine1 = prompt("Prg1>:").flatMap {_ => getLine}
val readLine2 = prompt("Prg2>:").flatMap {_ => getLine}
val stdOutput = constant(putLine _).toSource
def multiplyBy(n: Int) = await1[String].flatMap {line =>
if (line.isEmpty) halt
else emit((line.toDouble * n).toString)
}.repeat
val prg1: Process[Task,String] = {
def go(line: String): Process[Task,String] = line.toUpperCase match {
case "QUIT" => halt
case _ => emit(line) ++ await(readLine1)(go)
}
await(readLine1)(go)
}.onComplete{ Process.eval(Task.delay {println("end of program1"); ""}) }
val prg2: Process[Task,String] = {
def go(line: String): Process[Task,String] = line.toUpperCase match {
case "QUIT" => halt
case _ => emit(line) ++ await(readLine2)(go)
}
await(readLine2)(go)
}.onComplete{ Process.eval(Task.delay {println("end of program2"); ""}) }
val program1 = (prg1 |> multiplyBy() to stdOutput)
val program2 = (prg2 |> multiplyBy() to stdOutput) (program1 ++ program2).run.run }

因为program的类型是Process[Task,String],所以我们可以用++把它们连接起来。同时我们应该看到在program的形成过程中transducer multiplyBy是如何用|>与prg组合的。现在我们看看测试运算结果:

 Prg1>:
9.0
Prg1>:
12.0
Prg1>:quit
end of program1
Prg2>:
25.0
Prg2>:
30.0
Prg2>:quit
end of program2

我们看到程序是按照流程走的。下面再试个流程控制程序分发(dispatching)的例子:

  val program1 = (prg1 |> multiplyBy() observe stdOutput)
val program2 = (prg2 |> multiplyBy() observe stdOutput) //(program1 ++ program2).run.run
val getOption = prompt("Enter your choice>:").flatMap {_ => getLine }
val mainPrg: Process[Task,String] = {
def go(input: String): Process[Task,String] = input.toUpperCase match {
case "QUIT" => halt
case "P1" => program1 ++ await(getOption)(go)
case "P2" => program2 ++ await(getOption)(go)
case _ => await(getOption)(go)
}
await(getOption)(go)
}.onComplete{ Process.eval(Task.delay {println("end of main"); ""}) } mainPrg.run.run

我们先把program1和program2的终点类型Sink去掉。用observe来实现数据复制分流。这样program1和program2的结果类型才能与await的类型相匹配。我们可以测试运行一下:

 Enter your choice>:p2
Prg2>:
15.0
Prg2>:
25.0
Prg2>:quit
end of program2
Enter your choice>:p1
Prg1>:
9.0
Prg1>:
18.0
Prg1>:quit
end of program1
Enter your choice>:wat
Enter your choice>:oh no
Enter your choice>:quit
end of main

scalaz-stream是一种泛函类型。我们在上面已经示范了它的函数组合能力。当然,如果程序的类型是Process,那么我们可以很容易地用merge来实现并行运算。

scalaz-stream作为一种程序运算框架可以轻松实现FP程序的组合,那么它成为一种安全稳定的泛函多线程编程工具就会是很好的选择。

Scalaz(53)- scalaz-stream: 程序运算器-application scenario的更多相关文章

  1. 《java小应用程序(Applet)和java应用程序(Application)分别编写的简单计算器》

    Application和Java Applet的区别.Java语言是一种半编译半解释的语言.Java的用户程序分为两类:Java Application和Java Applet.这两类程序在组成结构和 ...

  2. 《Java应用程序(Application)》

    在编写Java应用程序(Application)时可以这样: 1,定义包名. 2, 导入相关的包. 3, 定义一个类. 4,定义相关变量. 5,定义构造函数.(在构造函数内调用init()方法和add ...

  3. delphi关闭程序Close,application.Terminate与halt区别

    当Close是一个主窗体时,程序会退出.Close会发生FormClose事件,FormCloseQuery事件Halt会发生FormDestory事件,Application.Terminate以上 ...

  4. 【应用程序见解 Application Insights】Application Insights 使用 Application Maps 构建请求链路视图

    Applicaotn  Insigths 使用 Application Maps 构建请求链路视图 构建系统时,请求的逻辑操作大多数情况下都需要在不同的服务,或接口中完成整个请求链路.一个请求可以经历 ...

  5. 【Azure 事件中心】为应用程序网关(Application Gateway with WAF) 配置诊断日志,发送到事件中心

    问题描述 在Application Gateway中,开启WAF(Web application firewall)后,现在需要把访问的日志输出到第三方分析代码中进行分析,如何来获取WAF的诊断日志呢 ...

  6. HTML5应用程序缓存Application Cache

    什么是Application Cache HTML5引入了应用程序缓存技术,意味着web应用可进行缓存,并在没有网络的情况下使用,通过创建cache manifest文件,可以轻松的创建离线应用. A ...

  7. HTML5应用程序缓存Application Cache详解

    什么是Application Cache HTML5引入了应用程序缓存技术,意味着web应用可进行缓存,并在没有网络的情况下使用,通过创建cache manifest文件,可以轻松的创建离线应用. A ...

  8. 在Ubuntu上为Android系统内置Java应用程序测试Application Frameworks层的硬件服务(老罗学习笔记6)

    一:Eclipse下 1.创建工程: ---- 2.创建后目录 3.添加java函数 4.在src下创建package,在package下创建file 5.res---layout下创建xml文件,命 ...

  9. 为Android系统内置Java应用程序测试Application Frameworks层的硬件服务

    我们在Android系统增加硬件服务的目的是为了让应用层的APP能够通过Java接口来访问硬件服务.那么, APP如何通过Java接口来访问Application Frameworks层提供的硬件服务 ...

随机推荐

  1. java 线程的终止与线程中断

    关于线程终止: 1.一般来讲线程在执行完毕后就会进入死亡状态,那该线程自然就终止了. 2.一些服务端的程序,可能在业务上需要,常驻系统.它本身是一个无穷的循环,用于提供服务.那对于这种线程我们该如何结 ...

  2. VMware Workstation cannot connect to the virtual machine 解决方案

    今天 打开虚拟机 忽然遇到这个问题: VMware Workstation cannot connect to the virtual machine. Make sure you have righ ...

  3. SoapUI测试WS接口实战

    引文: 本文讨论以下问题: 视频播放功能如何进行压力测试? 进行webservices接口测试时,用LR和soapui哪个工具更好? 1 测试需求 前几天接到一项压力测试的任务:视频播放功能的并发压力 ...

  4. 快速入门系列--NOSQL--05Redis也木有那么“高富帅”

    由于工作慢慢从原来的少量用户的企业内部应用慢慢转化为了大量用户的企业内部应用或者直接转为了线上高并发应用,因而也渐渐的开始使用memcached.Redis等缓存服务器,为了便于自身的学习和记忆,特此 ...

  5. JQuery uploadify 的使用

    在Java WEB项目中用到了上传图片的功能,于是百度了一下,发现 uploadify 的出镜率很高,于是决定使用这个插件.结果昨天调试了一天没有成功,今天早上仔细想了想,觉得应该是调用js文件的原因 ...

  6. 为什么很多网站的表单提交按钮都是用<a>标签

    之 前看过一些文章,说是用a标签来代替submit按钮更好一些,因为submit按钮在IE6下会有一些兼容的问题,不好统一,而且input的行高在各 浏览器参差不齐,还不能用line-height控制 ...

  7. WebForms UnobtrusiveValidationMode 需要“jquery”ScriptResourceMapping。请添加一个名为 jquery (区分大小写)的 ScriptResourceMapping。

    WebForms UnobtrusiveValidationMode 需要“jquery”ScriptResourceMapping.请添加一个名为 jquery (区分大小写)的 ScriptRes ...

  8. 【踩坑经历】一次Asp.NET小网站部署踩坑和解决经历

    2013年给1个大学的小客户部署过一个小型的Asp.NET网站,非常小,用的sqlite数据库,今年人家说要换台服务器,要重新部署一下,好吧,虽然早就过了服务时间,但无奈谁叫人家是客户了,二话不说,上 ...

  9. 邻接矩阵有向图(二)之 C++详解

    本章是通过C++实现邻接矩阵有向图. 目录 1. 邻接矩阵有向图的介绍 2. 邻接矩阵有向图的代码说明 3. 邻接矩阵有向图的完整源码 转载请注明出处:http://www.cnblogs.com/s ...

  10. ios见习之-UISearchbar+tableview实现自动搜索自带提示

    当做搜索时常常希望能在输入的时候出现搜索关键字,如下效果