Scalaz(53)- scalaz-stream: 程序运算器-application scenario
从上面多篇的讨论中我们了解到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的更多相关文章
- 《java小应用程序(Applet)和java应用程序(Application)分别编写的简单计算器》
Application和Java Applet的区别.Java语言是一种半编译半解释的语言.Java的用户程序分为两类:Java Application和Java Applet.这两类程序在组成结构和 ...
- 《Java应用程序(Application)》
在编写Java应用程序(Application)时可以这样: 1,定义包名. 2, 导入相关的包. 3, 定义一个类. 4,定义相关变量. 5,定义构造函数.(在构造函数内调用init()方法和add ...
- delphi关闭程序Close,application.Terminate与halt区别
当Close是一个主窗体时,程序会退出.Close会发生FormClose事件,FormCloseQuery事件Halt会发生FormDestory事件,Application.Terminate以上 ...
- 【应用程序见解 Application Insights】Application Insights 使用 Application Maps 构建请求链路视图
Applicaotn Insigths 使用 Application Maps 构建请求链路视图 构建系统时,请求的逻辑操作大多数情况下都需要在不同的服务,或接口中完成整个请求链路.一个请求可以经历 ...
- 【Azure 事件中心】为应用程序网关(Application Gateway with WAF) 配置诊断日志,发送到事件中心
问题描述 在Application Gateway中,开启WAF(Web application firewall)后,现在需要把访问的日志输出到第三方分析代码中进行分析,如何来获取WAF的诊断日志呢 ...
- HTML5应用程序缓存Application Cache
什么是Application Cache HTML5引入了应用程序缓存技术,意味着web应用可进行缓存,并在没有网络的情况下使用,通过创建cache manifest文件,可以轻松的创建离线应用. A ...
- HTML5应用程序缓存Application Cache详解
什么是Application Cache HTML5引入了应用程序缓存技术,意味着web应用可进行缓存,并在没有网络的情况下使用,通过创建cache manifest文件,可以轻松的创建离线应用. A ...
- 在Ubuntu上为Android系统内置Java应用程序测试Application Frameworks层的硬件服务(老罗学习笔记6)
一:Eclipse下 1.创建工程: ---- 2.创建后目录 3.添加java函数 4.在src下创建package,在package下创建file 5.res---layout下创建xml文件,命 ...
- 为Android系统内置Java应用程序测试Application Frameworks层的硬件服务
我们在Android系统增加硬件服务的目的是为了让应用层的APP能够通过Java接口来访问硬件服务.那么, APP如何通过Java接口来访问Application Frameworks层提供的硬件服务 ...
随机推荐
- [转]五种开源协议的比较(BSD,Apache,GPL,LGPL,MIT)
当Adobe.Microsoft.Sun等一系列巨头开始表现出对"开源"的青睐时,"开源"的时代即将到来!现今存在的开源协议很多,而经过Open Source ...
- Atitit 图像处理知识点 知识体系 知识图谱
Atitit 图像处理知识点 知识体系 知识图谱 图像处理知识点 图像处理知识点体系 v2 qb24.xlsx 基本知识图像金字塔op膨胀叠加混合变暗识别与检测分类肤色检测other验证码生成 基本 ...
- Atitit hsv转grb 应该优先使用hsv颜色原则 方便人类
Atitit hsv转grb 应该优先使用hsv颜色原则 方便人类 1.1. 1.1.hsv色卡1 1.2. 从 HSV 到 RGB 的转换1 1.3. HSVtoRGBColorV22 1.1. ...
- JS---DOM操作有哪一些
一 DOM对象有哪一些 1 windos 1.属性 opener 2.方法 open(),close() 例:<script langguage="javascript&qu ...
- 每天一个linux命令(36):diff 命令
diff 命令是 linux上非常重要的工具,用于比较文件的内容,特别是比较两个版本不同的文件以找到改动的地方.diff在命令行中打印每一个行的改动.最新版本的diff还支持二进制文件.diff程序的 ...
- 让 “微软雅黑” 在IE6下完美显示
微软雅黑可以说是网页中最常见的字体了,但是往往在IE8+的浏览器上调试得很好,到了IE6则会变成默认的"宋体". 因为宋体字体宽度通常比雅黑的要宽,有时候会把页面都布局挤乱. 为了 ...
- Python数据类型之“集合(Sets)与映射(Mapping)”
一.集合类型(Sets) 集合对象是不同的(不可重复)hashable对象的无序集合.常见用法包括:成员关系测试.移除序列中的重复.以及科学计算,例如交集.并集.差分和对称差分.通俗点来说,集合是一个 ...
- Pycharm远程调试
1.在pycharm的安装目录中找到pycharm-debug.egg,将其拷贝到目标主机的/usr/lib/python2.7/dist-packages目录下: 执行: sudo easy_ins ...
- Ubuntu14中supervisor的安装及配置
supervisor是一款很好用的进程管理工具,其命令也很简单,其安装过程如下: Ubuntu14: 首先保证本地的Python环境是OK的,并且已经安装supervisor包,如果没有安装可以用ea ...
- Html与CSS快速入门03-CSS基础应用
这部分是html细节知识的学习. 快速入门系列--HTML-01简介 快速入门系列--HTML-02基础元素 快速入门系列--HTML-03高级元素和布局 快速入门系列--HTML-04进阶概念 边框 ...