上期我们讨论了IO处理过程:Process[I,O]。我们说Process就像电视信号盒子一样有输入端和输出端两头。Process之间可以用一个Process的输出端与另一个Process的输入端连接起来形成一串具备多项数据处理功能的完整IO过程。但合成的IO过程两头输入端则需要接到一个数据源,而另外一端则可能会接到一个数据接收设备如文件、显示屏等。我们在这篇简单地先介绍一下IO数据源Source和IO数据接收端Sink。

我们先用一个独立的数据类型来代表数据源Source进行简单的示范说明,这个类型与Process类型没有任何关系:

 import ProcessLib._
object SourceSink {
trait Source[O] { //以下helper function都是把Source当作O类的List处理
def |>[O2](p: Process[O,O2]): Source[O2] //粘接一个Process p. 向其输入O
def filter(f: O => Boolean): Source[O] = this |> Process.filter(f) //向p输入O
def map[O2](f: O => O2): Source[O2] = this |> Process.lift(f)
def take(n: Int): Source[O] = this |> Process.take(n) //截取前n个O
def takeWhile(f: O => Boolean): Source[O] = this |> Process.takeWhile(f)
def drop(n: Int): Source[O] = this |> Process.drop(n) //跳过前n个O
def dropWhile(f: O => Boolean): Source[O] = this |> Process.dropWhile(f)
}

从以上trait可以看到:Source的工作原理就是把一个Process的输入黏贴到Source的输出端。我们可以用这个 |> 把一串Process粘到Source的输出,如:Src.proc1.proc2.proc3。不过我们得先把proc1,proc2,proc3定义成Source组件函数,因为Source是一个完全独立的类型。

我们再来看看一个Source特殊案例:

 case class ResourceR[R,I,O](   //Source的一个只读资源案例
acquire: IO[R], //资源使用门户 resource handle
release: R => IO[Unit], //完成使用资源后的清理函数
step: R => IO[Option[I]], //资源内容读取函数
trans: Process[I,O] //输出方式
) extends Source[O] {
def |>[O2](p: Process[O,O2]): Source[O2] = //实现抽象函数
ResourceR(acquire,release,step,trans |> p) //每次输入都产生一个ResourceR.它的trans与p进行管道对接
}

这是个只读的数据源。我们看到所有的动作都被包嵌在IO类型里,这样可以把副作用的产生延后到一些Source Interpreter来运算。这里我们只要用最简单的IO来说明就可以了:

 trait IO[A] { self =>
def run: A
def map[B](f: A => B): IO[B] =
new IO[B] { def run = f(self.run) }
def flatMap[B](f: A => IO[B]): IO[B] =
new IO[B] { def run = f(self.run).run }
}
object IO {
def unit[A](a: => A): IO[A] = new IO[A] { def run = a }
def flatMap[A,B](fa: IO[A])(f: A => IO[B]) = fa flatMap f
def apply[A](a: => A): IO[A] = unit(a) // syntax for IO { .. }
}

这个IO类型我们在前面的讨论里曾经练习过。

现在我们来看看一个文件读取的ResourceR例子:

 object Source {
import java.io._
def lines(fileName: String): Source[String] = //从文件fileName里读取String
ResourceR( //创建一个Source的实例
IO {io.Source.fromFile(fileName) }, //资源
(src: io.Source) => IO { src.close }, //清理
(src: io.Source) => IO { //读取
lazy val iterator = src.getLines
if (iterator.hasNext) Some(iterator.next) else None //读完返回None
},
Process.passUnchanged) //Process[I,I],读什么输入什么
}

现在我们可以这样写一段程序了:

  Source.lines("input.txt").count.exists{_ >= 40000 }
//> res0: ch15.SourceSink.Source[Boolean] = ResourceR(ch15.SourceSink$IO$$anon$
//| 3@762efe5d,<function1>,<function1>,Await(<function1>))

噢,记住把count和exists放到Source trait里:

     def exists(f: O => Boolean): Source[Boolean] = this |> Process.exists(f)
def count: Source[Int] = this |> Process.count

上面的表达式可以说还只是IO过程的描述。实际副作用产生是在interpreter里:

     def collect: IO[IndexedSeq[O]] = {  //读取数据源返回IO[IndexedSeq[O]], 用IO.run来实际运算
def tryOr[A](a: => A)(cleanup: IO[Unit]): A = //运算表达式a, 出现异常立即清理现场
try a catch {case e: Exception => cleanup.run; throw e}
@annotation.tailrec //这是个尾递归算法,根据trans状态
def go(acc: IndexedSeq[O], cleanup: IO[Unit], step: IO[Option[I]], trans: Process[I,O]): IndexedSeq[O] =
trans match {
case Halt() => cleanup.run; acc //停止状态,清理现场
case Emit(out,next) => go(tryOr(out +: acc)(cleanup), cleanup, step, next) //积累acc
case Await(iproc) => tryOr(step.run)(cleanup) match {
case None => cleanup.run; acc //读完了清理现场
case si => go(acc,cleanup,step,iproc(si)) //读入元素作为Process输入来改变Process状态
}
}
acquire map {res => go(IndexedSeq(),release(res),step(res),trans)} //开始读取
}

注意:无论读取完成或中途失败退出都会导致现场清理以防止资源漏出。可以推断这个interpreter还是很安全的。

与Source同样,我们还是用一个独立的类型Sink来代表数据接收端进行简单说明:

 trait Sink[I] {
def <|[I2](p: Process[I2,I]): Sink[I2] //p的输出接到Sink的输入
def filter(f: I => Boolean): Sink[I] = this <| Process.filter(f) //从p接收I
def map[I2](f: I2 => I): Sink[I2] = this <| Process.lift(f) //将接收的I2变成I
def take(n: Int): Sink[I] = this <| Process.take(n) //从p接收前n个I
def takeWhile(f: I => Boolean): Sink[I] = this <| Process.takeWhile(f)
def drop(n: Int): Sink[I] = this <| Process.drop(n) //过滤掉首n个I
def dropWhile(f: I => Boolean): Sink[I] = this <| Process.dropWhile(f)
}

这和Source trait及其相似。注意和Process连接是反向的:由p指向Sink。

同样,一个只写的资源实例如下:

 case class ResourceW[R,I,I2](  //只写资源
acquire: IO[R], //资源使用门户, resource handle
release: R => IO[Unit], //清理函数
rcvf: R => (I2 => IO[Unit]), //接收方式
trans: Process[I,I2] //处理过程
) extends Sink[I] {
def <|[I2](p: Process[I2,I]): Sink[I2] =
ResourceW(acquire,release,rcvf,p |> trans) //制造一个ResourceW实例,由p到trans
}

这个也和ResourceR相似。还是与Process连接方式是反方向的:由p到trans。

以下是一个向文件写入的Sink组件:

 object Sink {
import java.io._
def file(fileName: String, append: Boolean = false): Sink[String] = //结果是Sink[String]。必须用interpreter来运算
ResourceW( //是一个ResourceW实例
IO {new FileWriter(fileName,append) }, //创建FileWriter
(w: FileWriter) => IO {w.close}, //释放FileWriter
(w: FileWriter) => (s: String) => IO {w.write(s)}, //写入
Process.passUnchanged //不处理写入数据
)
}

在学习过程中发现,独立于Process类型的Source,Sink类型使IO算法的表达式类型的集成很困难。这也限制了组件的功能。我们无法实现泛函编程简洁高雅的表达形式。在下面的讨论中我们会集中精力分析具备数据源功能的Process,希望在表达方式上能有所进步。

泛函编程(36)-泛函Stream IO:IO数据源-IO Source & Sink的更多相关文章

  1. 泛函编程(5)-数据结构(Functional Data Structures)

    编程即是编制对数据进行运算的过程.特殊的运算必须用特定的数据结构来支持有效运算.如果没有数据结构的支持,我们就只能为每条数据申明一个内存地址了,然后使用这些地址来操作这些数据,也就是我们熟悉的申明变量 ...

  2. 泛函编程(38)-泛函Stream IO:IO Process in action

    在前面的几节讨论里我们终于得出了一个概括又通用的IO Process类型Process[F[_],O].这个类型同时可以代表数据源(Source)和数据终端(Sink).在这节讨论里我们将针对Proc ...

  3. 泛函编程(35)-泛函Stream IO:IO处理过程-IO Process

    IO处理可以说是计算机技术的核心.不是吗?使用计算机的目的就是希望它对输入数据进行运算后向我们输出计算结果.所谓Stream IO简单来说就是对一串按序相同类型的输入数据进行处理后输出计算结果.输入数 ...

  4. 泛函编程(37)-泛函Stream IO:通用的IO处理过程-Free Process

    在上两篇讨论中我们介绍了IO Process:Process[I,O],它的工作原理.函数组合等.很容易想象,一个完整的IO程序是由 数据源+处理过程+数据终点: Source->Process ...

  5. 泛函编程(32)-泛函IO:IO Monad

    由于泛函编程非常重视函数组合(function composition),任何带有副作用(side effect)的函数都无法实现函数组合,所以必须把包含外界影响(effectful)副作用不纯代码( ...

  6. 泛函编程(30)-泛函IO:Free Monad-Monad生产线

    在上节我们介绍了Trampoline.它主要是为了解决堆栈溢出(StackOverflow)错误而设计的.Trampoline类型是一种数据结构,它的设计思路是以heap换stack:对应传统递归算法 ...

  7. 泛函编程(27)-泛函编程模式-Monad Transformer

    经过了一段时间的学习,我们了解了一系列泛函数据类型.我们知道,在所有编程语言中,数据类型是支持软件编程的基础.同样,泛函数据类型Foldable,Monoid,Functor,Applicative, ...

  8. 二、linux IO 编程---系统调用和POSIX标准和标准IO

    2.1 系统调用 2.1.1 概念 所谓系统调用(system call)是指曹错系统提供给用户程序的一组“特殊”接口,用户程序可以通过这组“特殊”接口来获得操作系统内核提供的特殊服务. 应用程序可以 ...

  9. 泛函编程(29)-泛函实用结构:Trampoline-不再怕StackOverflow

    泛函编程方式其中一个特点就是普遍地使用递归算法,而且有些地方还无法避免使用递归算法.比如说flatMap就是一种推进式的递归算法,没了它就无法使用for-comprehension,那么泛函编程也就无 ...

随机推荐

  1. C#Color对象的使用介绍及颜色对照表

    原文地址  http://blog.sina.com.cn/s/blog_3e1177090101bzs3.html 今天用到了特转载 NET框架中的颜色基于4种成份,透明度,红,绿和蓝.每一种成份都 ...

  2. Atitit 发帖机系列(8)  词法分析器v5 版本新特性说明)

    Atitit 发帖机系列(8)  词法分析器v5 版本新特性说明) v5  增加对sql单引号的内部支持.可以作为string 结构调整,使用递归法重构循环发..放弃循环发. V4 java dsl词 ...

  3. Atitit.java expression fsm 表达式词法分析引擎 v2 qaa.docx

    Atitit.java expression fsm 表达式词法分析引擎 v2 qaa.docx C:\0workspace\AtiPlatf_cms\src\com\attilax\fsm\Java ...

  4. XML学习笔记1——概述

    我对于XML是很不够重视的,认识也是非常肤浅的,因为在之前的Web经验中,基本上都可以使用JSON来代替XML,JSON网络流量少,解析快,JS支持好等这些特点让我对自己的观点坚信不疑.然而我渐渐地改 ...

  5. SSIS 对数据排序

    SSIS 对数据排序有两种方式,一种是使用Sort组件,一种是使用sql command的order by clause进行排序. 一,使用Sort组件进行排序 SortType:升序 ascendi ...

  6. Oracle 11g系列:SQL Plus与PL/SQL

    SQL Plus是Oracle提供的一种用户接口,是数据库与用户之间进行交互的工具. PL/SQL是Oracle的过程化编程语言. 1.登录SQL Plus 利用Windows的[开始]|[运行]命令 ...

  7. JSP网站开发基础总结《六》

    对于本篇需要总结的内容,量估计有点大,大家好好看哈,绝对全是干货,代码的已经运行测试,不存在问题,大家可以参考学习,下面开始本篇的内容. 1.添加数据到数据库: 如何用户在JSP页面的填写的信息输入到 ...

  8. 关于CPU Cache -- 程序员需要知道的那些事

    本文将介绍一些作为程序猿或者IT从业者应该知道的CPU Cache相关的知识.本章从"为什么会有CPU Cache","CPU Cache的大致设计架构",&q ...

  9. Oracle常见错误

    1. Oracle无法动态注册 在SQL*PLUS下多次输入alter system register.监听器的状态始终显示为The listener supports no services. 可查 ...

  10. [转载]TFS发送邮件提醒功能

    第一次使用TFS 2010,发现有Project Alerts功能,就是项目组工程中若有任何改动时,TFS Server会自动发邮件提醒.Microsoft提供的配置方法(http://msdn.mi ...