泛函编程(35)-泛函Stream IO:IO处理过程-IO Process
IO处理可以说是计算机技术的核心。不是吗?使用计算机的目的就是希望它对输入数据进行运算后向我们输出计算结果。所谓Stream IO简单来说就是对一串按序相同类型的输入数据进行处理后输出计算结果。输入数据源可能是一串键盘字符、鼠标位置坐标、文件字符行、数据库纪录等。如何实现泛函模式的Stream IO处理则是泛函编程不可或缺的技术。
首先,我们先看一段较熟悉的IO程序:
import java.io._
def linesGt4k(fileName: String): IO[Boolean] = IO {
val src = io.Source.fromFile(fileName)
try {
var count =
val lines: Iterator[String] = src.getLines
while (count <= && lines.hasNext) {
lines.next
count +=
}
count >
} finally src.close
} //> linesGt4k: (fileName: String)fpinscala.iomonad.IO[Boolean]
以上例子里有几项是值得提倡的:使用完文件后及时关闭,防止资源流露、没有一次性将整个文件载入内存而是逐行读取文件内容,节省内存资源。虽然整个过程是包嵌在IO类型内,但操作代码直接产生副作用。很明显,起码IO处理过程是由非纯代码组成的,无法实现函数组合,既是无法实现泛函编程的通过重复使用组件灵活组合功能的特点了。可以相像,我们在泛函Stream IO编程中将会通过许多细小组件的各式组合来实现多样性的IO计算功能。
实际上我们想使用以下模式的表达式:
object examples {
//假设我们已经获取了这个Stream[String]
val lines: Stream[String] = sys.error("defined elsewhere!")
//无论40k或者其它数量都很容易得取。只要换个数字就行了
val lgt40k = lines.zipWithIndex.exists(_._2 + >= )
//把空行过滤掉
val lgt40k2 = lines.filter(! _.trim.isEmpty).zipWithIndex.exists(_._2 + >= )
//在40k行内检查是否存在连续11行第一个字母组合为abracadabra
val lgt40k3 = lines.take().map(_.head).indexOfSlice("abracadabra".toList)
}
以上代码充分显示了我们所追求的泛函编程模式:简洁、灵活、优雅。
不过,这个Stream[String]就不是表面上那么容易得到的了。我们先把它放一放。
我们现在可以先分析一下泛函Stream IO编程原理。泛函编程的精髓就是把一个程序分解成许多纯代码组件,然后通过各种搭配组合来实现程序整体功能。那么对于Stream IO计算我们希望能先实现那些纯代码的基本组件然后把它们组合起来。我们可以把Stream IO处理过程想象成连成一串的电视信号处理盒子:每个盒子具备一项信号转换或者过滤功能,我们将一个盒子的输出端子接入另一个盒子的输入端子使信号可以经过一连串的处理过程最终输出我们要求的信号。我们可以用一个IO处理过程代表一个信号转换盒子。它的款式是这样的;Process[I,O]。最终的IO程序就是一连串Process[I,O]。当然,第一个Process[I,O]的输入端必须连接一个Stream,而最后一个则接在一个实体的设备。我们先不管这两头,先从Process[I,O]的功能着手,使其能够连成一串并把输入类型I转变成输出类型O。
Process[I,O]的类型款式如下:
trait Process[I,O]{}
case class Halt[I,O]() extends Process[I,O]
case class Emit[I,O](head: O, tail: Process[I,O] = Halt[I,O]()) extends Process[I,O]
case class Await[I,O](rcvfn: Option[I] => Process[I,O]) extends Process[I,O]
每个Process[I,O]都可能处于三种状态之一:
1、Halt() 停止处理IO,退出。
2、Emit(head: O,tail: Process[I,O] = Halt[I,O]()) 输出类型O元素head,进入下一状态tail,默认输出head后完成退出。
3、Await(rcvfn: Option[I] => Process[I,O]) 等待一个类型I元素输入,处理IO,返回Process类型结果
可以看出,Await状态代表了某个Process的功能。Emit只是输出该Process对IO处理的结果。
注意:虽然Process[I,O]的功能是把Stream[I]转变成Stream[O],但它绝不是Stream[I] => Stream[O]类型的函数,而是在以上三种状态中游走的状态机器(State Machine)。
以下代码例子可以作为示范:
trait Process[I,O] {
def apply(sin: Stream[I]): Stream[O] = this match {
case Halt() => Stream() //返回空的Stream
case Emit(out,next) => out #:: next(sin) //先输出out,跟着处理剔除out的Stream[I]输入
case Await(iproc) => sin match {
case h #:: t_stream => iproc(Some(h))(t_stream) //如果sin不为空,接受输入首元素后返回状态为处理剔除首元素的Stream[I]输入
case xs => iproc(None)(xs) //如果sin为空则返回处理空输入状态
}
}
}
按照讨论题目,以上例子中Stream[I]被转变成Stream[O],而实现方式则是按照具体状态来确定输出。
为了实现函数组合(functional composition),我们必须想办法把两个Process像接水管一样连接起来:一头的输出是另一头的输入(function fusion):
def |>[O2](p2: Process[O,O2]): Process[I,O2] = //p2的输入类型是this的输出O,最终输出为p2的输出O2
p2 match {
case Halt() => Halt() //下面的动作停了,整个管道都停了
case Emit(out,next) => Emit(out, this |> next) //如果正在输出就先输出然后再连接剩下的数据
case Await(iproc) => this match { //如果下游正在等待输入元素,那么就要看上游是什么情况了
case Halt() => Halt() //如果上游停顿那么整个管道都停
case Emit(out,next) => next |> iproc(Some(out)) //上游正在输出,下游收到后进入新状态
case Await(rcvfn) => Await((oi: Option[I]) => rcvfn(oi) |> p2) //假如上游收到输入元素,立即转入新状态再继续连接
}
}
以上程序并不难理解。现在我们可以这样编写IO处理语句:proc1 |> proc2 |> proc3。
另外,可以把两个Process的处理过程连接起来:一个Process处理完后接着处理另一个Process:
def ++(p2: Process[I,O]): Process[I,O] = //完成了this后接着再运算p2
this match {
case Halt() => p2 //上一个Process完成后接着运算p2
case Emit(out,next) => Emit(out, next ++ p2) //等上游完成所有输出后再运算p2
case Await(iproc) => Await(iproc andThen (_ ++ p2)) //等上游处理完输入后再运算p2
}
最基本的一些组件map,flatMap:
def map[O2](f: O => O2): Process[I,O2] = //map Process的输出O
this match {
case Halt() => Halt() //没什么可以map的
case Emit(out,next) => Emit(f(out),next map f) //先map输入元素,再处理剩下的
case Await(iproc) => Await(iproc andThen (_ map f)) //处理完输入元素后再进行map
}
def flatMap[O2](f: O => Process[I,O2]): Process[I,O2] = //只处理输出端O
this match {
case Halt() => Halt()
case Emit(out,next) => f(out) ++ next.flatMap(f) //先处理头元素再flatMap剩下的
case Await(iproc) => Await(iproc andThen (_ flatMap f)) //处理完输入后再flatMap剩下的
}
我们再试试把一串元素喂入Process:
def feed(ins: Seq[I]): Process[I,O] = {
@annotation.tailrec
def go(ins: Seq[I], curProcess: Process[I,O]): Process[I,O] = //尾递归算法
curProcess match {
case Halt() => Halt()
case Emit(out,next) => Emit(out, next.feed(ins)) //正在输出。就等完成输出后再喂剩下的
case Await(iproc) => {
if (ins.isEmpty) curProcess //完成了输入元素串,可以返回结果了
else go(ins.tail,iproc(Some(ins.head))) //吃下首元素然后再继续
}
}
go(ins,this)
}
有时我们希望能重复一些简单的过程:
def repeat: Process[I,O] = { //永远重复下去
def go(p: Process[I,O]): Process[I,O] = //p代表当前更新状态
p match {
case Halt() => go(this) //不要停,重新再来
case Emit(out,next) => Emit(out, go(next)) //完成输出后继续go
case Await(iproc) => Await { //注意{}里是partialfunction。iproc是个函数,而partialfunction是function的子类,因而可以这样写
case None => iproc(None) //没有输入元素,继续等
case Some(i) => go(iproc(Some(i))) //处理输入元素后转入新状态然后继续
}
}
go(this)
}
def repeatN(n: Int): Process[I,O] = { //重复n次
def go(n: Int, curProcess: Process[I,O]): Process[I,O] =
curProcess match {
case Halt() => if (n <= ) Halt() //n次后真停
else go(n-, curProcess) //算一次重复
case Emit(out,next) => Emit(out, go(n,next)) //虽然状态更新了,但未完成流程。还不算一次重复
case Await(iproc) => Await {
case None => iproc(None) //继续等
case Some(i) => go(n,iproc(Some(i))) //更新了状态,但未完成流程,不算一次重复
}
}
go(n,this)
}
注意我们在以上代码中使用了PartialFunction来分解输入参数值。如果我们有个Function : intFunction(i: Int): String
我们可以定义它的PartialFunction:
{ case 0: "Zero"
case 10: "Ten" }
由于Await(iproc)中的iproc >>> Option[I] => Process[I,O], PartialFunction是Function的子类所以我们可以写成:
Await {
case None => ???
case Some(i) => ???
}
下面是一组Process的基本方法和组件:
object Process {
case class Halt[I,O]() extends Process[I,O]
case class Emit[I,O](head: O, tail: Process[I,O] = Halt[I,O]()) extends Process[I,O]
case class Await[I,O](rcvfn: Option[I] => Process[I,O]) extends Process[I,O] def emit[I,O](out: O, next: Process[I,O] = Halt[I,O]()) = Emit(out, next)
def await[I,O](iproc: I => Process[I,O], fallback: Process[I,O] = Halt[I,O]): Process[I,O] =
Await {
case Some(i) => iproc(i) //使用基本类型I
case None => fallback //定义了没有输入元素时应该怎么处理
}
}
我们可以把任何 I => O类型的函数升格成Process[I,O]:
def liftOnce[I,O](f: I => O): Process[I,O] = //给我一个I=>O,我返回Process[I,O]
Await {
case Some(i) => emit(f(i)) //等到一个输入元素I。把它升成一个状态为输出的Process
case None => Halt()
}
def repeatLift[I,O](f: I => O): Process[I,O] = liftOnce(f).repeat
def lift[I,O](f: I => O): Process[I,O] = //不同实现方式的repeatLift
Await {
case Some(i) => emit(f(i), lift(f))
case None => Halt()
}
还有些组件可以对输入元素进行过滤的:
def filter[I](f: I => Boolean): Process[I,I] = //对输入I进行过滤,不转变I, 所以结果是: Process[I,I]
Await[I,I] { //用PartialFunction来分解两种输入参数值面对的情况
case None => Halt[I,I]() //没有输入,停止
case Some(i) if(f(i)) => Emit[I,I](i)
}.repeat //重复过滤所有输入元素
def take[I](n: Int): Process[I,I] = //可以中途退出
if (n <= ) Halt[I,I]()
else Await[I,I] { //进行输入、输出这种IO操作
case None => Halt[I,I]() //没有输入就完成退出
case Some(i) => Emit[I,I](i,take[I](n-)) //输出通过过滤的,继续过滤剩下的输入元素
}
def takeWhile[I](f: I => Boolean): Process[I,I] = //可以中途退出
Await[I,I] {
case None => Halt[I,I]() //没有输入就完成退出
case Some(i) if(f(i)) => Emit[I,I](i, takeWhile[I](f))
}
def sendAsIs[I]: Process[I,I] = lift(identity) //直接输出任何输入元素
def drop[I](n: Int): Process[I,I] = //必须浏览所有输入元素。不可中途退出
if (n <= ) sendAsIs[I]
else Await[I,I](i => drop[I](n-)) //收取输入元素,直接扔掉,继续n-1循环
def dropWhile[I](f: I => Boolean): Process[I,I] = //必须浏览所有输入元素。不可中途退出
await(i => if (f(i)) dropWhile[I](f) //注意用await, 不是Await
else emit(i, sendAsIs[I])) //输出这个元素后继续循环输入元素
注意以上代码中的处理方式:如果过滤通过才emit,原封不动直接传递输入元素 I => I 用lift(identity)产生Process[I,I],用PartialFunction:
Await {
case None => ???
case Some(i) =>
}
来分别处理可能出现的输入参数值。
我们先尝试些简单的算法:
def count[I]: Process[I,Int] = //读取输入元素次数
//读入任何东西都转成数字1.0 |> 读一个加一个 |> 读入一个就转成一个Int
lift((i: I) => 1.0 ) |> sum |> lift(_.toInt) //每一个输入元素都会走完整个管道
def count2[I]: Process[I,Int] = { //递归实现方式
def go(c: Int): Process[I,Int] =
await((i: I) => emit(c+1, go(c+1)))
go(0)
}
def mean: Process[Double,Double] = {
def go(s: Double, c: Double): Process[Double,Double] =
await((d: Double) => emit((s+d)/(c+1), go(s+d,c+1)))
go(0.0,0.0)
}
//以上的内部函数go都体现了一些共同点:有一个起始值,然后维护状态。我们可以分解出一个新的函数
def loop[S,I,O](z: S)(f: (I,S) => (O,S)): Process[I,O] =
await((i: I) => f(i,z) match {
case (o,s2) => emit(o, loop(s2)(f))
})
//用loop来实现上面的函数
def sum2: Process[Double,Double] =
loop(0.0)((i:Double,s) => (s+i,s+1))
def count3[I]: Process[I,Int] =
loop(0)((_: I, c) => (c+1, c+1))
再写一些逻辑小组件:
def any: Process[Boolean, Boolean] = //检查是否收到过true值。即使收到true还是会继续收取输入直至完成读取
loop(false)((b: Boolean, s) => ( b || s, b || s))
def exists[I](f: I => Boolean): Process[I,Boolean] = //不能中途退出
lift(f) |> any //重复检查输入然后确定是否true. 一旦遇到true永远返回true
def echo[I]: Process[I,I] = await(i => emit(i))
def skip[I,O]: Process[I,O] = await(i => Halt())
def ignore[I,O]: Process[I,O] = skip.repeat
也可以过滤输出元素:
def filter(f: O => Boolean): Process[I,O] = //过滤输出元素
this |> Process.filter(f) //this的输出接到下一个Process的输入端然后过滤它的输入元素
zip两个Process:
def feedOne[I,O](oi: Option[I])(p: Process[I,O]): Process[I,O] = //把一个元素输入p
p match {
case Halt() => p //无法输入,它还是它
case Emit(out,next) => Emit(out, feedOne(oi)(next)) //正在输出。输出完当前元素再开始喂入
case Await(iproc) => iproc(oi) //直接喂入
} def zip[I,O,O2](p1: Process[I,O], p2: Process[I,O2]): Process[I,(O,O2)] = //同一串输入元素同时喂入p1,p2。合并输出2tuple
(p1,p2) match {
case (Halt(), _) => Halt()
case (_, Halt()) => Halt()
case (Emit(h1,t1), Emit(h2,t2)) => Emit((h1,h2), zip(t1,t2))
case (Await(iproc), _) => Await((oi: Option[I]) => zip(iproc(oi), feedOne(oi)(p2)))
case (_, Await(iproc)) => Await((oi: Option[I]) => zip(feedOne(oi)(p1), iproc(oi)))
}
val mean2 = zip[Double,Double,Int](sum,count) |> lift {case (s,c) => s/c}
还有那个熟悉的zipWithIndex:
def zip[O2](p2: Process[I,O2]): Process[I,(O,O2)] =
Process.zip(this,p2)
def zipWithIndex: Process[I,(O,Int)] =
this zip (count map {_ + 1}) //zip从0开始
现在我们肯定可以使用这样的表达式:
count |> exists {_ > 40000}。
当然我们还没有开始讨论这个管道两头的数据源。因为我们要分开独立讨论它。
下面是以上示范代码汇总:
trait Process[I,O] {
import Process._
def apply(sin: Stream[I]): Stream[O] = this match {
case Halt() => Stream() //返回空的Stream
case Emit(out,next) => out #:: next(sin) //先输出out,跟着处理剔除out的Stream[I]输入
case Await(iproc) => sin match {
case h #:: t_stream => iproc(Some(h))(t_stream) //如果sin不为空,接受输入首元素后返回状态为处理剔除首元素的Stream[I]输入
case xs => iproc(None)(xs) //如果sin为空则返回处理空输入状态
}
}
def |>[O2](p2: Process[O,O2]): Process[I,O2] = //p2的输入类型是this的输出O,最终输出为p2的输出O2
p2 match {
case Halt() => Halt() //下面的动作停了,整个管道都停了
case Emit(out,next) => Emit(out, this |> next) //如果正在输出就先输出然后再连接剩下的数据
case Await(iproc) => this match { //如果下游正在等待输入元素,那么就要看上游是什么情况了
case Halt() => Halt() //如果上游停顿那么整个管道都停
case Emit(out,next) => next |> iproc(Some(out)) //上游正在输出,下游收到后进入新状态
case Await(rcvfn) => Await((oi: Option[I]) => rcvfn(oi) |> p2) //假如上游收到输入元素,立即转入新状态再继续连接
}
}
def ++(p2: Process[I,O]): Process[I,O] = //完成了this后接着再运算p2
this match {
case Halt() => p2 //上一个Process完成后接着运算p2
case Emit(out,next) => Emit(out, next ++ p2) //等上游完成所有输出后再运算p2
case Await(iproc) => Await(iproc andThen (_ ++ p2)) //等上游处理完输入后再运算p2
}
def map[O2](f: O => O2): Process[I,O2] = //map Process的输出O
this match {
case Halt() => Halt() //没什么可以map的
case Emit(out,next) => Emit(f(out),next map f) //先map输入元素,再处理剩下的
case Await(iproc) => Await(iproc andThen (_ map f)) //处理完输入元素后再进行map
}
def flatMap[O2](f: O => Process[I,O2]): Process[I,O2] = //只处理输出端O
this match {
case Halt() => Halt()
case Emit(out,next) => f(out) ++ next.flatMap(f) //先处理头元素再flatMap剩下的
case Await(iproc) => Await(iproc andThen (_ flatMap f)) //处理完输入后再flatMap剩下的
}
def feed(ins: Seq[I]): Process[I,O] = {
@annotation.tailrec
def go(ins: Seq[I], curProcess: Process[I,O]): Process[I,O] = //尾递归算法
curProcess match {
case Halt() => Halt()
case Emit(out,next) => Emit(out, next.feed(ins)) //正在输出。就等完成输出后再喂剩下的
case Await(iproc) => {
if (ins.isEmpty) curProcess //完成了输入元素串,可以返回结果了
else go(ins.tail,iproc(Some(ins.head))) //吃下首元素然后再继续
}
}
go(ins,this)
}
def repeat: Process[I,O] = { //永远重复下去
def go(p: Process[I,O]): Process[I,O] = //p代表当前更新状态
p match {
case Halt() => go(this) //不要停,重新再来
case Emit(out,next) => Emit(out, go(next)) //完成输出后继续go
case Await(iproc) => Await { //注意{}里是partialfunction。iproc是个函数,而partialfunction是function的子类,因而可以这样写
case None => iproc(None) //没有输入元素,继续等
case Some(i) => go(iproc(Some(i))) //处理输入元素后转入新状态然后继续
}
}
go(this)
}
def repeatN(n: Int): Process[I,O] = { //重复n次
def go(n: Int, curProcess: Process[I,O]): Process[I,O] =
curProcess match {
case Halt() => if (n <= 0) Halt() //n次后真停
else go(n-1, curProcess) //算一次重复
case Emit(out,next) => Emit(out, go(n,next)) //虽然状态更新了,但未完成流程。还不算一次重复
case Await(iproc) => Await {
case None => iproc(None) //继续等
case Some(i) => go(n,iproc(Some(i))) //更新了状态,但未完成流程,不算一次重复
}
}
go(n,this)
}
def filter(f: O => Boolean): Process[I,O] = //过滤输出元素
this |> Process.filter(f) //this的输出接到下一个Process的输入端然后过滤它的输入元素
def orElse(p: Process[I,O]): Process[I,O] =
this match {
case Halt() => p
case Await(iproc) => Await {
case None => p
case x => iproc(x)
}
case _ => this
}
def zip[O2](p2: Process[I,O2]): Process[I,(O,O2)] =
Process.zip(this,p2)
def zipWithIndex: Process[I,(O,Int)] =
this zip (count map {_ + 1}) //zip从0开始
}
object Process {
case class Halt[I,O]() extends Process[I,O]
case class Emit[I,O](head: O, tail: Process[I,O] = Halt[I,O]()) extends Process[I,O]
case class Await[I,O](rcvfn: Option[I] => Process[I,O]) extends Process[I,O] def emit[I,O](out: O, next: Process[I,O] = Halt[I,O]()) = Emit(out, next)
def await[I,O](iproc: I => Process[I,O], fallback: Process[I,O] = Halt[I,O]): Process[I,O] =
Await {
case Some(i) => iproc(i) //使用基本类型I
case None => fallback //定义了没有输入元素时应该怎么处理
}
def liftOnce[I,O](f: I => O): Process[I,O] = //给我一个I=>O,我返回Process[I,O]
Await {
case Some(i) => emit(f(i)) //等到一个输入元素I。把它升成一个状态为输出的Process
case None => Halt()
}
def repeatLift[I,O](f: I => O): Process[I,O] = liftOnce(f).repeat
def lift[I,O](f: I => O): Process[I,O] = //不同实现方式的repeatLift
Await {
case Some(i) => emit(f(i), lift(f))
case None => Halt()
}
def filter[I](f: I => Boolean): Process[I,I] = //对输入I进行过滤,不转变I, 所以结果是: Process[I,I]
Await[I,I] { //用PartialFunction来分解两种输入参数值面对的情况
case None => Halt[I,I]() //没有输入,停止
case Some(i) if(f(i)) => Emit[I,I](i)
}.repeat //重复过滤所有输入元素
def take[I](n: Int): Process[I,I] = //可以中途退出
if (n <= 0) Halt[I,I]()
else Await[I,I] { //进行输入、输出这种IO操作
case None => Halt[I,I]() //没有输入就完成退出
case Some(i) => Emit[I,I](i,take[I](n-1)) //输出通过过滤的,继续过滤剩下的输入元素
}
def takeWhile[I](f: I => Boolean): Process[I,I] = //可以中途退出
Await[I,I] {
case None => Halt[I,I]() //没有输入就完成退出
case Some(i) if(f(i)) => Emit[I,I](i, takeWhile[I](f))
}
def sendAsIs[I]: Process[I,I] = lift(identity) //直接输出任何输入元素
def drop[I](n: Int): Process[I,I] = //必须浏览所有输入元素。不可中途退出
if (n <= 0) sendAsIs[I]
else Await[I,I](i => drop[I](n-1)) //收取输入元素,直接扔掉,继续n-1循环
def dropWhile[I](f: I => Boolean): Process[I,I] = //必须浏览所有输入元素。不可中途退出
await(i => if (f(i)) dropWhile[I](f) //注意用await, 不是Await
else emit(i, sendAsIs[I])) //输出这个元素后继续循环输入元素 def sum: Process[Double,Double] = { //读进数字,输出当前总数
def go(acc: Double): Process[Double,Double] =
await(d => emit(acc+d, go(acc+d)))
go(0.0)
}
def count[I]: Process[I,Int] = //读取输入元素次数
//读入任何东西都转成数字1.0 |> 读一个加一个 |> 读入一个就转成一个Int
lift((i: I) => 1.0 ) |> sum |> lift(_.toInt) //每一个输入元素都会走完整个管道
def count2[I]: Process[I,Int] = { //递归实现方式
def go(c: Int): Process[I,Int] =
await((i: I) => emit(c+1, go(c+1)))
go(0)
}
def mean: Process[Double,Double] = {
def go(s: Double, c: Double): Process[Double,Double] =
await((d: Double) => emit((s+d)/(c+1), go(s+d,c+1)))
go(0.0,0.0)
}
//以上的内部函数go都体现了一些共同点:有一个起始值,然后维护状态。我们可以分解出一个新的函数
def loop[S,I,O](z: S)(f: (I,S) => (O,S)): Process[I,O] =
await((i: I) => f(i,z) match {
case (o,s2) => emit(o, loop(s2)(f))
})
//用loop来实现上面的函数
def sum2: Process[Double,Double] =
loop(0.0)((i:Double,s) => (s+i,s+1))
def count3[I]: Process[I,Int] =
loop(0)((_: I, c) => (c+1, c+1))
def any: Process[Boolean, Boolean] = //检查是否收到过true值。即使收到true还是会继续收取输入直至完成读取
loop(false)((b: Boolean, s) => ( b || s, b || s))
def exists[I](f: I => Boolean): Process[I,Boolean] = //不能中途退出
lift(f) |> any //重复检查输入然后确定是否true. 一旦遇到true永远返回true
def echo[I]: Process[I,I] = await(i => emit(i))
def skip[I,O]: Process[I,O] = await(i => Halt())
def ignore[I,O]: Process[I,O] = skip.repeat def feedOne[I,O](oi: Option[I])(p: Process[I,O]): Process[I,O] = //把一个元素输入p
p match {
case Halt() => p //无法输入,它还是它
case Emit(out,next) => Emit(out, feedOne(oi)(next)) //正在输出。输出完当前元素再开始喂入
case Await(iproc) => iproc(oi) //直接喂入
} def zip[I,O,O2](p1: Process[I,O], p2: Process[I,O2]): Process[I,(O,O2)] = //同一串输入元素同时喂入p1,p2。合并输出2tuple
(p1,p2) match {
case (Halt(), _) => Halt()
case (_, Halt()) => Halt()
case (Emit(h1,t1), Emit(h2,t2)) => Emit((h1,h2), zip(t1,t2))
case (Await(iproc), _) => Await((oi: Option[I]) => zip(iproc(oi), feedOne(oi)(p2)))
case (_, Await(iproc)) => Await((oi: Option[I]) => zip(feedOne(oi)(p1), iproc(oi)))
}
val mean2 = zip[Double,Double,Int](sum,count) |> lift {case (s,c) => s/c} count |> exists {_ > 40000}
}
泛函编程(35)-泛函Stream IO:IO处理过程-IO Process的更多相关文章
- 泛函编程(5)-数据结构(Functional Data Structures)
编程即是编制对数据进行运算的过程.特殊的运算必须用特定的数据结构来支持有效运算.如果没有数据结构的支持,我们就只能为每条数据申明一个内存地址了,然后使用这些地址来操作这些数据,也就是我们熟悉的申明变量 ...
- 泛函编程(38)-泛函Stream IO:IO Process in action
在前面的几节讨论里我们终于得出了一个概括又通用的IO Process类型Process[F[_],O].这个类型同时可以代表数据源(Source)和数据终端(Sink).在这节讨论里我们将针对Proc ...
- 泛函编程(36)-泛函Stream IO:IO数据源-IO Source & Sink
上期我们讨论了IO处理过程:Process[I,O].我们说Process就像电视信号盒子一样有输入端和输出端两头.Process之间可以用一个Process的输出端与另一个Process的输入端连接 ...
- 泛函编程(37)-泛函Stream IO:通用的IO处理过程-Free Process
在上两篇讨论中我们介绍了IO Process:Process[I,O],它的工作原理.函数组合等.很容易想象,一个完整的IO程序是由 数据源+处理过程+数据终点: Source->Process ...
- 泛函编程(32)-泛函IO:IO Monad
由于泛函编程非常重视函数组合(function composition),任何带有副作用(side effect)的函数都无法实现函数组合,所以必须把包含外界影响(effectful)副作用不纯代码( ...
- 泛函编程(30)-泛函IO:Free Monad-Monad生产线
在上节我们介绍了Trampoline.它主要是为了解决堆栈溢出(StackOverflow)错误而设计的.Trampoline类型是一种数据结构,它的设计思路是以heap换stack:对应传统递归算法 ...
- 泛函编程(27)-泛函编程模式-Monad Transformer
经过了一段时间的学习,我们了解了一系列泛函数据类型.我们知道,在所有编程语言中,数据类型是支持软件编程的基础.同样,泛函数据类型Foldable,Monoid,Functor,Applicative, ...
- 【linux草鞋应用编程系列】_1_ 开篇_系统调用IO接口与标准IO接口
最近学习linux系统下的应用编程,参考书籍是那本称为神书的<Unix环境高级编程>,个人感觉神书不是写给草鞋看的,而是 写给大神看的,如果没有一定的基础那么看这本书可能会感到有些头重脚轻 ...
- 泛函编程(23)-泛函数据类型-Monad
简单来说:Monad就是泛函编程中最概括通用的数据模型(高阶数据类型).它不但涵盖了所有基础类型(primitive types)的泛函行为及操作,而且任何高阶类或者自定义类一旦具备Monad特性就可 ...
随机推荐
- 制作Html标签以及表单、表格内容
制作Html一般用DW(......),Html全称为(Hyper Text Markup Language 超文本标记语言) 文本就是平常电脑上的文本文档,只能存储文字,而超文本文档可以存储音乐 ...
- golang开发缓存组件
代码地址github:cache 花了一天时间看了下实验楼的cache组件,使用golang编写的,收获还是蛮多的,缓存组件的设计其实挺简单的,主要思路或者设计点如下: 全局struct对象:用来做缓 ...
- 每天一个linux命令(19):find 命令概览
Linux下find命令在目录结构中搜索文件,并执行指定的操作.Linux下find命令提供了相当多的查找条件,功能很强大.由于find具有强大的功能,所以它的选项也很多,其中大部分选项都值得我们花时 ...
- Netty学习三:线程模型
1 Proactor和Reactor Proactor和Reactor是两种经典的多路复用I/O模型,主要用于在高并发.高吞吐量的环境中进行I/O处理. I/O多路复用机制都依赖于一个事件分发器,事件 ...
- CSS实现垂直居中的4种思路
× 目录 [1]line-height [2]vertical-align [3]absolute [4]flex 前面的话 相对于水平居中,人们对于垂直居中略显为难,大部分原因是vertical-a ...
- Android线程之AsyncTask
在之前的博客中为大家分享过关于Android多线程处理,想必大家对于Android为什么要使用多线程已经有了清晰的认识,我就在简单唠两句,Android规定UI界面的更新必须在在主线程进行,对于访问网 ...
- Spring学习总结(五)——Spring整合MyBatis(Maven+MySQL)二
接着上一篇博客<Spring整合MyBatis(Maven+MySQL)一>继续. Spring的开放性和扩张性在J2EE应用领域得到了充分的证明,与其他优秀框架无缝的集成是Spring最 ...
- Ruby FFI 入门教程
FFI是一个可以让用户使用Ruby调用C代码的gem.如果你需要执行一些系统底层调用,或者做一些高性能运算的话,FFI是一个很不错的选择. 1. 安装 执行gem install ffi即可.非常标准 ...
- java io系列16之 PrintStream(打印输出流)详解
本章介绍PrintStream以及 它与DataOutputStream的区别.我们先对PrintStream有个大致认识,然后再深入学习它的源码,最后通过示例加深对它的了解. 转载请注明出处:htt ...
- js每天进步一点点2
JS之图片的切换效果