泛函编程(16)-泛函状态-Functional State
初接触泛函状态觉着很不习惯。主要是在使用State数据类型时很难理解其中的原理,特别是泛函状态变迁机制(state transition mechanism):怎么状态就起了变化,实在难以跟踪。我想这主要是因为状态变迁机制经过了函数组合,已经深深的埋藏在运行代码后面。上节我们讨论到RNG,对于了解State类型是个很好的开头。RNG简单描述了泛函方式的状态变迁及支持状态变迁所需要的数据结构和操作函数款式。
在上节我们提到过 type Rand[+A] = RNG => (A, RNG),Rand是一个随意数产生函数。由于Rand是个类型,一个函数类型,所以可以被当作参数或者返回值来使用。我们把这个定义再扩展一下,变得更通用一些:type State[S, +A] = S => (A, S)。Rand就是State的一个特殊案例:type Rand[+A] = State[RNG, +A] 。我们称State为状态行为,即S => (A,S)是一个定义状态变迁方式的函数。State类型的状态变迁机制就是通过状态行为函数来确定的。再次聚焦一下我们设计State类型的目标:State类型不但可以使我们像设计其它类型一样封装一个较低阶类型元素并且提供一套状态变迁机制,而且状态变迁机制是泛函式的,自然隐性的。
我们先试试简单的State类型设计:
case class State[S,+A](run: S => (A, S))
没错,就是这么简单,也是我刻意为之。注意状态行为函数run是State类的内部成员,我们有针对性的把一个State的状态变迁机制通过在构建State类时作为参数注入。然后产生的State实例就会按照我们期待的那样进行状态变迁了。case class自备了apply,这样我们可以直接使用State(???)创建State实例。我会把State(s => (a,s))写成State { s => (a,s)},这样表达传入的是一段代码会更形象自然一点。State[]既然是一个高阶类型,那么我们应该也为它提供一套在管子内部进行元素操作的函数。切记!切记!在处理管子内封装元素值的同时要按照状态行为函数的要求对类型状态进行相应变迁。
先从高阶类型最基本的组件开始:
object State {
def unit[S,A](a: A) = State[S,A](s => (a, s))
}
我们前面接触过这个unit。它就是一个封装元素值和状态都不转变的State实例。unit的唯一功能就是把低阶一级的封装元素类型a升格为State类型。
我们来编写一个State函数,切记!切记!要同时处理状态变迁机制:
case class State[S,+A](run: S => (A, S)) {
def flatMap[B](f: A => State[S,B]): State[S,B] = State[S,B] {
s => {
val (a, s1) = run(s)
f(a).run(s1)
}
}
在flatMap里我们用函数f处理了封装元素a, f(a)。同时我们又引用了状态行为函数run对传入的状态s进行了状态变迁 run(s)。
def map[B](f: A => B): State[S,B] = State[S,B] {
s => {
val (a, s1) = run(s)
(f(a),s1)
}
}
def map_1[B](f: A => B): State[S,B] = {
flatMap { a => unit(f(a)) }
}
同样,map也实施了f(a),run(s)。map也可以用flatMap来实现。它们之间的分别只是f: A => B 和 A => State[S,B]。因为我们有unit, unit(a) = State[S,A],unit(f(a)) = State[S,B]所以我们用unit把map的函数参数A升格就行了。用flatMap来实现map可以把map抽升到更高级:这样map就不用再理会那个状态行为函数了。
那么map2呢?
def map2[B,C](sb: State[S,B])(f: (A,B) => C): State[S,C] = {
flatMap {a => sb.map { b => f(a,b) }}
}
def map3[B,C,D](sb: State[S,B], sc: State[S,C])(f: (A,B,C) => D): State[S,D] = {
flatMap {a => sb.flatMap {b => sc.map { c => f(a,b,c) }}}
}
map2的功能是用封装元素类型函数(A,B) => C来把两个State管子里的元素结合起来。我们可以施用flatMap两次来把两个管子里的元素结合起来。对于map3我们可以再加一次。
另一种连续施用flatMap的表达方式:
def map2_1[B,C](sb: State[S,B])(f: (A,B) => C): State[S,C] ={
for {
a <- this
b <- sb
} yield f(a,b)
}
def map3_1[B,C,D](sb: State[S,B], sc: State[S,C])(f: (A,B,C) => D): State[S,D] ={
for {
a <- this
b <- sb
c <- sc
} yield f(a,b,c)
}
以上的语法糖(syntatic sugar)for-comprehension让我们俨然进入了一个泛函世界,好像有了一种兴奋的感觉。这种表达形式简洁直白,更加容易理解。同样,在map2,map3里没有涉及到任何状态变迁的东西。我们实现了状态变迁的隐形操作。
下面举个切实例子来示范泛函状态:
//Stack类型就是一个List[Int],后面比较容易表达点
type Stack = List[Int]
//pop就是一个State实例。它的状态行为函数是partial function:把一个现成的List[Int]拆分成新的值和状态
//即把第一个元素去掉放到值里
def pop = State[Stack, Int]{ case x::xs => (x, xs) }
//> pop: => ch6.state.State[ch6.state.Stack,Int]
//push就是一个State实例。它的状态行为函数把i压到一个现成的List[Int]上,跟值没有任何关系
def push(i: Int) = State[Stack, Unit]{ case xs => ((), i :: xs ) }
//> push: (i: Int)ch6.state.State[ch6.state.Stack,Unit]
def stackRun: State[Stack, Int] = {
for {
_ <- push(13)
a <- pop
b <- pop
} yield a+b
} //> stackRun: => ch6.state.State[ch6.state.Stack,Int] val (a, s) =stackRun.run(List(10,11,12)) //> a : Int = 23
//| s : ch6.state.Stack = List(11, 12)
在stackRun里我们没有在任何地方提到状态Stack,但看看运行结果(a,s):不但返回值是正确的,而且Stack状态也默默地发生了转变。如果尝试从stackRun的代码里去分析状态是如何转变的是永远无法理解的,建议还是老老实实从头来吧。
泛函状态是一种隐形自动的变迁,那么如果我们需要打乱既定流程,手动设定或者临时读取状态时该怎么办呢?
object State {
def unit[S,A](a: A) = State[S,A](s => (a, s))
def getState[S]: State[S,S] = State[S,S] { s => (s,s) }
def setState[S](s: S): State[S,Unit] = State[S,Unit] { _ => ((),s)}
}
还是通过状态行为函数来实现的。
def stackRun: State[Stack, Int] = {
for {
_ <- push(13)
a <- pop
_ <- setState(List(8,9))
b <- pop
s1 <- getState
} yield (a + b)
} //> stackRun: => ch6.state.State[ch6.state.Stack,Int]
val (a, s) =stackRun.run(List(10,11,12)) //> a : Int = 21
//| s : ch6.state.Stack = List(9)
我们可以临时将状态设置成List(8,9)。
泛函编程(16)-泛函状态-Functional State的更多相关文章
- 泛函编程(5)-数据结构(Functional Data Structures)
编程即是编制对数据进行运算的过程.特殊的运算必须用特定的数据结构来支持有效运算.如果没有数据结构的支持,我们就只能为每条数据申明一个内存地址了,然后使用这些地址来操作这些数据,也就是我们熟悉的申明变量 ...
- 泛函编程(17)-泛函状态-State In Action
对OOP编程人员来说,泛函状态State是一种全新的数据类型.我们在上节做了些介绍,在这节我们讨论一下State类型的应用:用一个具体的例子来示范如何使用State类型.以下是这个例子的具体描述: 模 ...
- 泛函编程(34)-泛函变量:处理状态转变-ST Monad
泛函编程的核心模式就是函数组合(compositionality).实现函数组合的必要条件之一就是参与组合的各方程序都必须是纯代码的(pure code).所谓纯代码就是程序中的所有表达式都必须是Re ...
- 泛函编程(35)-泛函Stream IO:IO处理过程-IO Process
IO处理可以说是计算机技术的核心.不是吗?使用计算机的目的就是希望它对输入数据进行运算后向我们输出计算结果.所谓Stream IO简单来说就是对一串按序相同类型的输入数据进行处理后输出计算结果.输入数 ...
- 泛函编程(38)-泛函Stream IO:IO Process in action
在前面的几节讨论里我们终于得出了一个概括又通用的IO Process类型Process[F[_],O].这个类型同时可以代表数据源(Source)和数据终端(Sink).在这节讨论里我们将针对Proc ...
- 泛函编程(29)-泛函实用结构:Trampoline-不再怕StackOverflow
泛函编程方式其中一个特点就是普遍地使用递归算法,而且有些地方还无法避免使用递归算法.比如说flatMap就是一种推进式的递归算法,没了它就无法使用for-comprehension,那么泛函编程也就无 ...
- 泛函编程(25)-泛函数据类型-Monad-Applicative
上两期我们讨论了Monad.我们说Monad是个最有概括性(抽象性)的泛函数据类型,它可以覆盖绝大多数数据类型.任何数据类型只要能实现flatMap+unit这组Monad最基本组件函数就可以变成Mo ...
- 泛函编程(24)-泛函数据类型-Monad, monadic programming
在上一节我们介绍了Monad.我们知道Monad是一个高度概括的抽象模型.好像创造Monad的目的是为了抽取各种数据类型的共性组件函数汇集成一套组件库从而避免重复编码.这些能对什么是Monad提供一个 ...
- 泛函编程(8)-数据结构-Tree
上节介绍了泛函数据结构List及相关的泛函编程函数设计使用,还附带了少许多态类型(Polymorphic Type)及变形(Type Variance)的介绍.有关Polymorphism的详细介绍会 ...
- 泛函编程(36)-泛函Stream IO:IO数据源-IO Source & Sink
上期我们讨论了IO处理过程:Process[I,O].我们说Process就像电视信号盒子一样有输入端和输出端两头.Process之间可以用一个Process的输出端与另一个Process的输入端连接 ...
随机推荐
- 让Mac也能拥有apt-get类似的功能——Brew
之前一直怀念ubuntu下的apt-get,因为实在是方便,需要安装什么,一个命令搞定,相关的依赖包统统由apt-get维护.下载,编译,安装,那叫一个痛快.什么软件用着不爽,一个命令卸载! 怀念ap ...
- java-cef系列视频第二集:搭建开发环境
上一集我们介绍了如何从官方代码编译java-cef. 本视频介绍了如何使用eclipse搭建java-cef二次开发环境. 下一集我们将给java-cef添加flash支持. 本作品采用知识共享署名- ...
- css3整理--clip
clip语法: .selector { clip: rect | auto | inherit } 注意:clip属性只能在元素设置了“position:absolute”或者“position:fi ...
- CSS - toggle collapse 类似bootstrap的展开效果
问题:toggle collapse 类似bootstrap的展开效果(展开一个关闭另一个) Demo:http://jsfiddle.net/JSDavi/L47vscw4/ 方案:使用transi ...
- 跟随标准与Webkit源码探究DOM -- 获取元素之getElementById
按照ID获取元素 -- getElementById 标准 DOM 1,定义在HTMLDocument Interface 中,原型Element getElementById(in DOMStrin ...
- [转]查看手机已经记住的WIFI密码
有时用过wifi后记住密码了,但再想知道wifi密码是多少,怎么办呢.下面的方法为你解决这样的问题. 1.手机必须取得root权限. 2.用RE管理器或es文件浏览器进入data/misc/wifi, ...
- 斐波那契堆(一)之 图文解析 和 C语言的实现
概要 本章介绍斐波那契堆.和以往一样,本文会先对斐波那契堆的理论知识进行简单介绍,然后给出C语言的实现.后续再分别给出C++和Java版本的实现:实现的语言虽不同,但是原理如出一辙,选择其中之一进行了 ...
- [转载]SharePoint 2013测试环境安装配置指南
软件版本 Windows Server 2012 标准版 SQL Server 2012 标准版 SharePoint Server 2013 企业版 Office Web Apps 2013 备注: ...
- WhatFontIs - 字体百科全书,没有不认识的字体
我敢肯定,我不是唯一一个曾经特别想知道图片上使用的某个字体,然后特别无奈的到字体网站大海捞针似的的找类似的字体.如今,一个强大的软件字体识别——WhatFontIs,让我们的生活更轻松. 您可能感兴趣 ...
- Android学习笔记之蓝牙通信...
PS:最近同学问我蓝牙的事,因此自己也就脑补了一下蓝牙... 学习内容: 1.如何实现蓝牙通信技术... 蓝牙通信其实是手机里很常用的一种通信方式,现在的手机中是必然存在蓝牙的,蓝牙通信也是有一部 ...