泛函编程(37)-泛函Stream IO:通用的IO处理过程-Free Process
在上两篇讨论中我们介绍了IO Process:Process[I,O],它的工作原理、函数组合等。很容易想象,一个完整的IO程序是由 数据源+处理过程+数据终点: Source->Process->Sink所组成的。我们发现:Process[I,O]本身是无法兼顾Source和Sink的功能。而独立附加的Source和Sink又无法有效地与Process[I,O]进行函数组合(functional composition)。
实际上Process[I,O]是一种固定单一输入类型(single input process):单一是指Process[I,O]只接收I类型输入、固定是指它对外界沟通方式是固定的:只有Halt,Emit,Await三种状态。这种情况导致了Process[I,O]无法成为有效的IO程序组件以及我们必须尝试开发更概括更通用的IO Process。我们来看个新的IO类型结构:Process[F[_],O]
- trait Process[F[_],O]{}
- object Process {
- case class Halt[F[_],O](err: Throwable) extends Process[F,O] //终止,err代表停止原因
- case class Emit[F[_],O](os: Seq[O], ns: Process[F,O]) extends Process[F,O] //发送os,ns=nextState
- case class Await[F[_],A,O]( //等待输入
- rq: F[A], //产生输入的运算。可能是有副作用的
- rf: Either[Throwable,A] => Process[F,O], //对运算F[A]返回值的处理函数
- fb: Process[F,O], //fallback 消耗完输入后转入fb状态
- fl: Process[F,O] //finalizer 清理现场,释放资源
- ) extends Process[F,O]
- }
这个新的类型代表能产生一连串的O类型元素。我们可以把它当作List[O]来编写组件。重要的是我们现在可以通过运算F[A]来与外界联系,如:读取文件或数据库内容。运算F[A]返回的数据经过rf函数的处理后转入新的状态:正常完成数据输入时运行fb进入新状态、如果输入数据过程中产生异常则运行fl清理战场,释放资源。我们并且改善了Halt,使之返回终止情况信息。这样一来Process从整体上更透明,更安全。更重要的是Process可以拓展了(extensible)。
作为一个安全可用的IO类型,我们先设计一些Process[F,O]的基本组件:
- case object End extends Exception //正常终止信号
- case object Kill extends Exception //手工强行终止信号
- //运算p,如果出现异常返回Halt使整个过程可控
- def Try[F[_],O](p: Process[F,O]): Process[F,O] =
- try p
- catch { case e: Throwable => Halt[F,O](e) } //返回异常信息
我们在运算Process时用Try来捕捉异常信息并返回到可控状态Halt(err),因为我们可以从返回状态了解终止情况 - End:正常终止,Kill:强行终止及Throwable:发生异常终止。
现在我们再设计一些方便调用的帮助函数(helper functions):
- //如果下一个状态还是emit ,那么就一次性合并发出,效率高些
- // emitAll(os1, Emit(os2,ns)) == Emit(os1 ++ os2, ns)
- def emitAll[F[_],O](outseq: Seq[O], nxtState: Process[F,O] = Halt[F,O](End)): Process[F,O] =
- nxtState match {
- case Emit(os,ns) => Emit(outseq ++ os, ns) //下一个状态是Emit, 合并输出
- case _ => Emit(outseq,nxtState) //其它情况照旧
- }
- //遇到单O值时方便使用
- def emit[F[_],O](o: O, nxs: Process[F,O] = Halt[F,O](End)): Process[F,O] = emitAll[F,O](Seq(o),nxs)
- //Await类的应用方便函数
- def await[F[_],A,O](req: F[A])(rcfn: Either[Throwable,A] => Process[F,O])(
- fallback: Process[F,O] = Halt[F,O](End),
- finalizer: Process[F,O] = Halt[F,O](End)) = Await(req,rcfn,fallback,finalizer)
还有更多的帮助函数:
- //在进入终止状态时运行f
- def onHalt(f: Throwable => Process[F,O]): Process[F,O] = this match {
- case Halt(e) => Try { f(e) } //当前状态是终止,运算f
- case Emit(os,ns) => emitAll(os,ns.onHalt(f)) //还未到终止,该干什么还干什么。移到下一状态再看
- case Await(rq,rf,fb,fl) => await(rq)(rf andThen (_ onHalt(f)))(fb,fl) //还未到终止,移到下一状态再看
- }
- //维持运行直至终止。但不输出任何数据
- def drain[O2]: Process[F,O2] = this match {
- case Halt(e) => Halt(e)
- case Emit(os,ns) => ns.drain
- case Await(rq,rf,fb,fl) => Await(rq,rf andThen (_ drain))
- }
- //正在执行清理环境,释放资源中,维持运行,不得人工终止
- def asFinalizer: Process[F,O] = this match {
- case Halt(e) => Halt(e)
- case Emit(os,ns) => Emit(os, ns.asFinalizer)
- case Await(rq,rf,fb,fl) => await(rq){
- case Left(Kill) => this.asFinalizer //如果人工终止,强行继续运行
- case x => rf(x)
- }(fb,fl)
- }
- //在终止时运算p,即使是出现了异常情况
- def onComplete(p: => Process[F,O]) =
- this.onHalt {
- case End => p.asFinalizer //正常终止
- case err => p.asFinalizer ++ Halt(err) //出现了异常,先运算p,再把异常信息累积起来
- }
在上面我们还调用了++函数,目的是把两个process连接起来。如:p.asFinalizer ++ Halt(err)。下面我们就把这个函数实现了:
- //连接两个process, this 终止后跟着运算 p
- def ++(p: Process[F,O]): Process[F,O] = this match {
- case Halt(e) => Try { p } //终止了,现在运算 p
- case Emit(os,ns) => emitAll(os, ns ++ p) //还未终止,延后连接
- case Await(rq,rf,fb,fl) =>Await(rq, rf andThen (_ ++ p), fb ++ p, fl ++ p) //还未终止,延后连接
- }
- //++的另一种实现方式。我们直接考虑终止状态
- def append(p: Process[F,O]): Process[F,O] =
- onHalt {
- case End => Try { p } //我们只会在正常终止的情况下继续运算p
- case err => Halt(err)
- }
还有我们熟悉的map,flatmap,repeat:
- def map[O2](f: O => O2): Process[F,O2] = this match {
- case Halt(e) => Halt(e)
- case Emit(os,ns) => Try { if (os.isEmpty) ns map f
- else emit(f(os.head), emitAll(os.tail, ns) map f) }
- case Await(rq,rf,fb,fl) => Await(rq, rf andThen (_ map f), fb map f, fl map f)
- }
- def flatMap[O2](f: O => Process[F,O2]): Process[F,O2] = this match {
- case Halt(e) => Halt(e)
- case Emit(os,ns) => Try { if (os.isEmpty) ns flatMap f
- else f(os.head) ++ emitAll(os.tail, ns).flatMap(f) }
- case Await(rq,rf,fb,fl) => Await(rq,rf andThen(_ flatMap f), fb flatMap f, fl flatMap f)
- }
- def repeat: Process[F,O] = this ++ this.repeat
注意:我们只针对O值的转变。顺便把保证运算安全的几个帮助函数列出来(上面我们已经尝试使用了Try):
- //运算p,如果出现异常返回Halt使整个过程可控
- def Try[F[_],O](p: Process[F,O]): Process[F,O] =
- try p
- catch { case e: Throwable => Halt[F,O](e) } //返回异常信息
- //运算p,出现任何异常都先运算fl然后终止
- def TryOr[F[_],O](p: Process[F,O], fl: Process[F,O]): Process[F,O] =
- try p
- catch {case e: Throwable => fl ++ Halt[F,O](e) }
- //运算p,如果正常终止,运行fb, 如果异常终止则运算fl
- def TryAwait[F[_],O](p: Process[F,O])(fb: Process[F,O], fl: Process[F,O]): Process[F,O] =
- try p
- catch {
- case End => fb //正常终止
- case e: Throwable => fl ++ Halt(e)
- }
既然我们说Process[F[_],O]是个更概括的IO类型,那么Process[I,O]应该是Process[F[_],O]的一个特殊案例。现在最重要的是我们需要塑造这个F把它限制在只能接受I类型输入:
- case class Is[I]() {
- sealed trait f[X] {}
- case object get extends f[I] //f[X]只有一个实例,就是f[I],所以 X就只能是I
- }
- def Get[I] = Is[I]().get
- type Process1[I,O] = Process[Is[I]#f,O]
- def halt1[I,O]: Process1[I,O] = Halt[Is[I]#f,O](End)
- def emit1[I,O](o: O, ns: Process1[I,O] = halt1[I,O]): Process1[I,O] = emit(o,ns)
- def emitAll1[I,O](os: Seq[O], ns: Process1[I,O] = halt1[I,O]): Process1[I,O] = emitAll(os,ns)
- def await1[I,O](rf: I => Process1[I,O],fb: Process1[I,O] = halt1[I,O],fl: Process1[I,O] = halt1[I,O]) =
- await(Get[I]){(ei: Either[Throwable,I]) => ei match { //F[A]>>>Get[I], 只接收I类型输入
- case Left(End) => fb
- case Left(err) => halt1[I,O]
- case Right(i) => Try { rf(i) }
- }}(fb,fl)
我们同样可以推导出之前针对Process[I,O]的那些组件:
- def lift[I,O](f: I => O): Process1[I,O] =
- await1[I,O](i => emit(f(i))) repeat
- def filter[I](f: I => Boolean): Process1[I,I] =
- await1[I,I](i => if (f(i)) emit(i) else halt1)
- def take[I](n: Int): Process1[I,I] =
- if (n > 0) await1[I,I](i => emit(i, take(n-1)))
- else halt1
- def takeWhile[I](f: I => Boolean): Process1[I,I] =
- await1[I,I](i => if (f(i)) emit(i, takeWhile(f)) else halt1)
- //循环入什么就出什么
- def id[I]: Process1[I,I] = await1[I,I](i => emit(i,id))
- def passUnchanged[I]: Process1[I,I] = lift[I,I](identity)
- def drop[I](n: Int): Process1[I,I] =
- if (n > 0) await1[I,I](i => drop[I](n-1))
- else passUnchanged
- def dropWhile[I](f: I => Boolean): Process1[I,I] =
- await1[I,I](i => if (f(i)) id else emit(i, dropWhile(f)))
注意:组件实现中的写法和之前Process[I,O]的一样,只不过这次我们的返回类型是Process1[I,O]。
现在我们来到了IO Process对接最重要的组件导管(pipe |>)组件了:
- //强行终止process
- @annotation.tailrec
- final def kill[O2]: Process[F,O2] = this match {
- case Halt(e) => Halt(e) //已经处于终止状态
- case Emit(os,ns) => ns.kill //发送中,忽略输出kill下个状态
- case Await(rq,rf,fb,fl) => rf(Left(Kill)).drain.onHalt { //如果正在等待输入,那么就给它输入Kill信号然后过滤余下的输入
- case Kill => Halt(End) //在终止前如果收到Kill信号就转入正常终止状态
- case err => Halt(err) //如果是异常终止
- }
- }
- //对接两个process. 上一个emit发送后下面的立即await接受
- def |>[O2](p2: Process1[O,O2]): Process[F,O2] = {
- @annotation.tailrec
- //把Seq[O]一个O一个O喂入p2
- def feed(os: Seq[O], ns: Process[F,O], rf: Either[Throwable,O] => Process1[O,O2],
- fb: Process1[O,O2] = halt1[O,O2], fl: Process1[O,O2] = halt1[O,O2]): Process[F,O2] =
- if (os.isEmpty) ns |> await(Get[O])(rf)(fb,fl)
- else rf(Right(os.head)) match { //喂进去一个元素后再观察下面的状态
- case Await(rq1,rf1,fb1,fl1) => feed(os.tail,ns,rf,fb,fl)
- case p => Emit(os.tail,ns) |> p
- }
- p2 match {
- case Halt(e) => this.kill.onHalt {e2 => Halt(e) ++ Halt(e2)} //如果下游管道读者了。杀掉上游并过滤所有输出
- case Emit(os,ns) => emitAll(os, this |> ns)
- case Await(rq,rf,fb,fl) => this match {
- case Halt(e) => Halt(e) |> p2
- case Await(rq0,rf0,fb0,fl0) => await(rq0)(rf0 andThen (_ |> p2))(fb0 |> p2, fl0 |> p2)
- case Emit(os,ns) => Try {feed(os,ns,rf,fb,fl)} //上游发送下游接受状态。正是对接状态
- }
- }
- }
我们只需要考虑下游p等待输入await同时上游this正在发送emit这一个状态实现对接。其它状态则等它们自己调整对口后再对接。
有了这个|>后我们可以把那些Process1组件对接到Process[F,O]上:
- def filter(f: O => Boolean): Process[F,O] = this |> Process.filter(f)
- def take(n: Int): Process[F,O] = this |> Process.take(n)
- def takeWhile(f: O => Boolean): Process[F,O] = this |> Process.takeWhile(f)
- def drop(n: Int): Process[F,O] = this |> Process.drop(n)
- def dropWhile(f: O => Boolean): Process[F,O] = this |> Process.dropWhile(f)
以上组件都是过滤输出O的。
我们还可以通过Process[F,O]实现两头输入:就像字母T,输入从上面左右两头进入。
首先,我们还是要重新塑造一下F[A],使它只容许左边I类,右边I2类输入:
- case class T[I,I2]() {
- sealed trait f[X] {def get: Either[I => X, I2 => X]}
- val L = new f[I] { def get = Left(identity) }
- val R = new f[I2] { def get = Right(identity) }
- }
- def L[I,I2] = T[I,I2]().L
- def R[I,I2] = T[I,I2]().R
- type Tee[I,I2,O] = Process[T[I,I2]#f,O]
Tee类型就是一个两头输入的IO Process类型,左边只可以输入I,右边只可以输入I2。
我们同样需要重新定义那些构建Tee的基本组件:
- def haltT[I,I2,O] = Halt[T[I,I2]#f,O](End)
- def emitT[I,I2,O](o: O, ns: Tee[I,I2,O] = haltT) = emit[T[I,I2]#f,O](o,ns)
- //左边输入
- def awaitL[I,I2,O](rf: I => Tee[I,I2,O], fb: Tee[I,I2,O] = haltT, fl: Tee[I,I2,O] = haltT): Tee[I,I2,O] =
- await[T[I,I2]#f,I,O](L){ //F[A] >>> L >>> 只容许I类输入
- case Left(End) => fb
- case Left(err) => fl ++ Halt(err)
- case Right(i) => Try { rf(i) }
- }(fb,fl)
- //右边输入
- def awaitR[I,I2,O](rf: I2 => Tee[I,I2,O], fb: Tee[I,I2,O] = haltT, fl: Tee[I,I2,O] = haltT): Tee[I,I2,O] =
- await[T[I,I2]#f,I2,O](R) { //F[A] >>> R >>> 只容许I2类输入
- case Left(End) => fb
- case Left(err) => fl ++ Halt(err)
- case Right(i2) => Try { rf(i2) }
- }(fb,fl)
zip是两边输入穿插动作。我们可以用这个Tee类型来实现zip:
- def zipWith[I,I2,O](f: (I,I2) => O): Tee[I,I2,O] =
- awaitL[I,I2,O](i => awaitR[I,I2,O](i2 => emitT(f(i,i2)))) repeat
- //两个输入交叉输出一个对值pair
- def zip[I,I2]: Tee[I,I2,(I,I2)] = zipWith[I,I2,(I,I2)]((_,_))
- //轮换从左右两边输入
- def interleave[I]: Tee[I,I,I] = awaitL[I,I,I](i => awaitR(i2 => emitT(i) ++ emitT(i2))) repeat
- //不理会右边输入,只取左边
- def passL[I,I2]: Tee[I,I2,I] = awaitL(emitT(_,passL))
- //不理会左边输入,只取右边
- def passR[I,I2]: Tee[I,I2,I2] = awaitR(emitT(_,passR))
- //完成左边输入后运算fb
- def awaitLOr[I,I2,O](fb: Tee[I,I2,O])(rf: I => Tee[I,I2,O]): Tee[I,I2,O] =
- awaitL(rf,fb)
- //完成右边输入后运算fb
- def awaitROr[I,I2,O](fb: Tee[I,I2,O])(rf: I2 => Tee[I,I2,O]): Tee[I,I2,O] =
- awaitR(rf,fb)
- //如果两边输入长度不同就在左边补填padI或右边补填padI2
- def zipWithAll[I,I2,O](padI: I, padI2: I2)(f: (I,I2) => O): Tee[I,I2,O] = {
- val fbL = passL[I,I2] map {f(_, padI2)} //假如先完成左边输入,运算fbL,返回补填padI2的Tee类型
- val fbR = passR[I,I2] map {f(padI, _)} //假如先完成右边输入,运算fbR,返回补填padI的Tee类型
- awaitLOr(fbL)(i =>
- awaitROr(fbR)(i2 => emitT(f(i,i2)))) repeat
- }
下面是Tee的一个通用概括的帮助函数,它是两头输入的代表形式:
- //接入左右两边输入
- def tee[O2,O3](p2: Process[F,O2])(t: Tee[O,O2,O3]): Process[F,O3] = {
- @annotation.tailrec
- def feedL(os: Seq[O], ns: Process[F,O], p: Process[F,O2], t: Tee[O,O2,O3], //从左边喂入
- rf: Either[Throwable,O] => Tee[O,O2,O3]): Process[F,O3] = //输入类型O
- if (os.isEmpty) (ns tee p)(t) //喂完所有输入元素
- else rf(Right(os.head)) match { //喂进一个O后检查状态
- case Await(rq0,rf0,fb0,fl0) => feedL(os.tail,ns,p,t,rf0) //下一个状态还是await的话继续喂os.tail
- case t0 => (Emit(os.tail,ns) tee p)(t0) //tee转入新状态
- }
- @annotation.tailrec
- def feedR(os: Seq[O2], ns: Process[F,O2], p: Process[F,O], t: Tee[O,O2,O3], //从右边喂入
- rf: Either[Throwable,O2] => Tee[O,O2,O3]): Process[F,O3] = //输入类型O2
- if (os.isEmpty) (p tee ns)(t) //喂完所有输入元素
- else rf(Right(os.head)) match { //喂进一个O2后检查状态
- case Await(rq0,rf0,fb0,fl0) => feedR(os.tail,ns,p,t,rf0) //下一个状态还是await的话继续喂os.tail
- case t0 => (p tee Emit(os.tail,ns))(t0) //tee转入新状态
- }
- t match {
- case Halt(e) => this.kill onComplete p2.kill onComplete Halt(e) //T管道停止就杀掉左右输入process
- case Emit(os,ns) => emitAll(os, (this tee p2)(ns)) //T正在发送,等发送完再tee
- case Await(side,rf,fb,fl) => side.get match { //T管道正在等待输入,先确定是左右那边输入
- case Left(isO) => this match { //左边输入,类型O
- case Halt(e) => p2.kill onComplete Halt(e) //左边输入终止,杀掉右边输入保持两边同步
- case Await(rqL,rfL,fbL,flL) => await(rqL)(rfL andThen (this2 => this2.tee(p2)(t)))() //左边也在等输入
- case Emit(os,ns) => Try {feedL(os,ns,p2,t,rf)} //左边在发送,用feedL逐个O喂入
- }
- case Right(isO2) => p2 match {
- case Halt(e) => this.kill onComplete Halt(e) //右边输入终止,杀掉左边输入保持两边同步
- case Await(rqR,rfR,fbR,flR) => await(rqR)(rfR andThen (p => this.tee(p)(t)))()
- case Emit(os,ns) => Try {feedR(os,ns,this,t,rf)} //右边在发送,用feedL逐个O喂入
- }
- }
- }
- }
现在zipWith可以这样写了:
- //用tee来实现zipWith
- def zipWith[O2,O3](p2: Process[F,O2])(f: (O,O2) => O3): Process[F,O3] =
- (this tee p2)(Process.zipWith(f))
一个完整的IO程序必须包括对数据源Source和数据终点Sink的操作,那么Process[F,O]可不可以代表数据源(Source)类型呢?我们来看看Process[F,O]的读取Await:
- case class Await[F[_],A,O]( //等待输入
- rq: F[A], //产生输入的运算。可能是有副作用的
- rf: Either[Throwable,A] => Process[F,O], //对运算F[A]返回值的处理函数
- fb: Process[F,O] = Halt[F,O](End), //fallback 消耗完输入后转入fb状态
- fl: Process[F,O] = Halt[F,O](End) //finalizer 清理现场,放开所有使用中的资源
- ) extends Process[F,O]
如果我们把F[A]换成IO[A]:
- case class Await[IO[_],A,O]( //等待输入
- rq: IO[A], //产生输入的运算。可能是有副作用的
- rf: Either[Throwable,A] => Process[IO,O], //对运算F[A]返回值的处理函数
- fb: Process[IO,O] = Halt[IO,O](End), //fallback 消耗完输入后转入fb状态
- fl: Process[IO,O] = Halt[IO,O](End) //finalizer 清理现场,放开所有使用中的资源
- ) extends Process[IO,O]
实际上Process[I,O]就是Process[F,O]的一个案例。我们只要运算IO就可以从数据源读取数据了(run IO 返回结果)。我们先看看IO类型,我们前面曾经讨论过这个类型:
- //简化的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 { .. }
- }
现在我们可以写个从数据源Source读取数据的程序了:
- //从数据源src把内容读入内存
- def collect[O](src: Process[IO,O]): IndexedSeq[O] = { //从src移到IndexedSeq
- def go(curState: Process[IO,O], accSeq: IndexedSeq[O]): IndexedSeq[O] =
- curState match {
- case Halt(e) => accSeq //完成,返回累积数据
- case Emit(os,ns) => go(ns, accSeq ++ os) //当前处于发送数据状态,把发送数据累积到accSeq
- case Await(rq,rf,fb,fl) => go(Try { rf(Right(rq.run)) }, accSeq) //正在读取数据中
- //如果读取发生异常Try函数会返回终止Halt(err)
- }
- go(src,IndexedSeq())
- }
注意以上只是示范了Process[IO,O]作为Process[F,O]的一个特殊实例是可以实现从Source读取数据的。我们将在下个章节讨论具体的可行方案。
我们下面再看看数据终点Sink与Process[F,O]的关系。我们希望通过Process[F,O]类型实现输出功能,也就是把Source的输入发送给Sink。我们用以下方式代表Sink:
type Sink[F[_],O] = Process[F, O => F[Unit]]
Source和Sink类型的实际应用介绍将在下期“IO过程实际应用-IO Process in action”中具体讨论。
泛函编程(37)-泛函Stream IO:通用的IO处理过程-Free Process的更多相关文章
- 泛函编程(5)-数据结构(Functional Data Structures)
编程即是编制对数据进行运算的过程.特殊的运算必须用特定的数据结构来支持有效运算.如果没有数据结构的支持,我们就只能为每条数据申明一个内存地址了,然后使用这些地址来操作这些数据,也就是我们熟悉的申明变量 ...
- 泛函编程(38)-泛函Stream IO:IO Process in action
在前面的几节讨论里我们终于得出了一个概括又通用的IO Process类型Process[F[_],O].这个类型同时可以代表数据源(Source)和数据终端(Sink).在这节讨论里我们将针对Proc ...
- 泛函编程(35)-泛函Stream IO:IO处理过程-IO Process
IO处理可以说是计算机技术的核心.不是吗?使用计算机的目的就是希望它对输入数据进行运算后向我们输出计算结果.所谓Stream IO简单来说就是对一串按序相同类型的输入数据进行处理后输出计算结果.输入数 ...
- 泛函编程(36)-泛函Stream IO:IO数据源-IO Source & Sink
上期我们讨论了IO处理过程:Process[I,O].我们说Process就像电视信号盒子一样有输入端和输出端两头.Process之间可以用一个Process的输出端与另一个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, ...
- 泛函编程(23)-泛函数据类型-Monad
简单来说:Monad就是泛函编程中最概括通用的数据模型(高阶数据类型).它不但涵盖了所有基础类型(primitive types)的泛函行为及操作,而且任何高阶类或者自定义类一旦具备Monad特性就可 ...
- 泛函编程(6)-数据结构-List基础
List是一种最普通的泛函数据结构,比较直观,有良好的示范基础.List就像一个管子,里面可以装载一长条任何类型的东西.如需要对管子里的东西进行处理,则必须在管子内按直线顺序一个一个的来,这符合泛函编 ...
随机推荐
- vue.js学习之入门实例
之前一直看过vue.js官网api,但是很少实践,这里抽出时间谢了个入门级的demo,记录下一些知识点,防止后续踩坑,牵扯到的的知识点:vue.vue-cli.vue-router.webpack等. ...
- 浅谈JAVA集合框架
浅谈JAVA集合框架 Java提供了数种持有对象的方式,包括语言内置的Array,还有就是utilities中提供的容器类(container classes),又称群集类(collection cl ...
- ui-router带参数的ui-sref配置
ui-router带参数的ui-sref配置 路由 .state('app.user_edit', { url:'user/userid/:userid', templateUrl: 'compone ...
- KnockoutJS 3.X API 第四章(13) template绑定
目的 template绑定(模板绑定)使用渲染模板的结果填充关联的DOM元素. 模板是一种简单方便的方式来构建复杂的UI结构 . 下面介绍两种使用模板绑定的方法: 本地模板是支持foreach,if, ...
- Netty学习五:Buffers
1. Netty中的缓冲 在Netty中并没有使用Java自带的ByteBuffer,而是自己实现提供了一个缓存区来用于标识一个字节序列,并帮助用户操作原始字节或者自定义的POJO. Java NIO ...
- C# 索引器使用总结
1.索引器(Indexer): 索引器允许类或者结构的实例按照与数组相同的方式进行索引.索引器类似于属性,不同之处在于他们的访问采用参数. 最简单的索引器的使用 /// <summary> ...
- ios见习之-UISearchbar+tableview实现自动搜索自带提示
当做搜索时常常希望能在输入的时候出现搜索关键字,如下效果
- Javascript定时器(三)——setTimeout(func, 0)
setTimeout(func, 0)可以使用在很多地方,拆分循环.模拟事件捕获.页面渲染等 一.setTimeout中的delay参数为0,并不是指马上执行 <script type=&quo ...
- [Node.js] DSL in action
原文地址:http://www.moye.me/2015/05/30/dsl-in-action/ 最近看了本有意思的书,受到了一些启发,在此记录一下: DSLs in action DSL是什么 ...
- Minimit Anima – 硬件加速的 CSS3 动画插件
Minimit Anima 是一个实现 CSS3 Transforms 和 Transitions 动画的 jQuery 插件.基于硬件加速的 CSS3 动画执行更快,而且它有一个类似于 jQuery ...