泛函编程(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类型设计:
1 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[]既然是一个高阶类型,那么我们应该也为它提供一套在管子内部进行元素操作的函数。切记!切记!在处理管子内封装元素值的同时要按照状态行为函数的要求对类型状态进行相应变迁。
先从高阶类型最基本的组件开始:
1 object State {
2 def unit[S,A](a: A) = State[S,A](s => (a, s))
3 }
我们前面接触过这个unit。它就是一个封装元素值和状态都不转变的State实例。unit的唯一功能就是把低阶一级的封装元素类型a升格为State类型。
我们来编写一个State函数,切记!切记!要同时处理状态变迁机制:
1 case class State[S,+A](run: S => (A, S)) {
2 def flatMap[B](f: A => State[S,B]): State[S,B] = State[S,B] {
3 s => {
4 val (a, s1) = run(s)
5 f(a).run(s1)
6 }
7 }
在flatMap里我们用函数f处理了封装元素a, f(a)。同时我们又引用了状态行为函数run对传入的状态s进行了状态变迁 run(s)。
1 def map[B](f: A => B): State[S,B] = State[S,B] {
2 s => {
3 val (a, s1) = run(s)
4 (f(a),s1)
5 }
6 }
7 def map_1[B](f: A => B): State[S,B] = {
8 flatMap { a => unit(f(a)) }
9 }
同样,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呢?
1 def map2[B,C](sb: State[S,B])(f: (A,B) => C): State[S,C] = {
2 flatMap {a => sb.map { b => f(a,b) }}
3 }
4 def map3[B,C,D](sb: State[S,B], sc: State[S,C])(f: (A,B,C) => D): State[S,D] = {
5 flatMap {a => sb.flatMap {b => sc.map { c => f(a,b,c) }}}
6 }
map2的功能是用封装元素类型函数(A,B) => C来把两个State管子里的元素结合起来。我们可以施用flatMap两次来把两个管子里的元素结合起来。对于map3我们可以再加一次。
另一种连续施用flatMap的表达方式:
1 def map2_1[B,C](sb: State[S,B])(f: (A,B) => C): State[S,C] ={2 for {3 a <- this4 b <- sb5 } yield f(a,b)6 }7 def map3_1[B,C,D](sb: State[S,B], sc: State[S,C])(f: (A,B,C) => D): State[S,D] ={8 for {9 a <- this
10 b <- sb
11 c <- sc
12 } yield f(a,b,c)
13 }
以上的语法糖(syntatic sugar)for-comprehension让我们俨然进入了一个泛函世界,好像有了一种兴奋的感觉。这种表达形式简洁直白,更加容易理解。同样,在map2,map3里没有涉及到任何状态变迁的东西。我们实现了状态变迁的隐形操作。
下面举个切实例子来示范泛函状态:
1 //Stack类型就是一个List[Int],后面比较容易表达点2 type Stack = List[Int]3 //pop就是一个State实例。它的状态行为函数是partial function:把一个现成的List[Int]拆分成新的值和状态4 //即把第一个元素去掉放到值里5 def pop = State[Stack, Int]{ case x::xs => (x, xs) }6 //> pop: => ch6.state.State[ch6.state.Stack,Int]7 //push就是一个State实例。它的状态行为函数把i压到一个现成的List[Int]上,跟值没有任何关系8 def push(i: Int) = State[Stack, Unit]{ case xs => ((), i :: xs ) }9 //> push: (i: Int)ch6.state.State[ch6.state.Stack,Unit]
10 def stackRun: State[Stack, Int] = {
11 for {
12 _ <- push(13)
13 a <- pop
14 b <- pop
15 } yield a+b
16 } //> stackRun: => ch6.state.State[ch6.state.Stack,Int]
17
18 val (a, s) =stackRun.run(List(10,11,12)) //> a : Int = 23
19 //| s : ch6.state.Stack = List(11, 12)
在stackRun里我们没有在任何地方提到状态Stack,但看看运行结果(a,s):不但返回值是正确的,而且Stack状态也默默地发生了转变。如果尝试从stackRun的代码里去分析状态是如何转变的是永远无法理解的,建议还是老老实实从头来吧。
泛函状态是一种隐形自动的变迁,那么如果我们需要打乱既定流程,手动设定或者临时读取状态时该怎么办呢?
1 object State {
2 def unit[S,A](a: A) = State[S,A](s => (a, s))
3 def getState[S]: State[S,S] = State[S,S] { s => (s,s) }
4 def setState[S](s: S): State[S,Unit] = State[S,Unit] { _ => ((),s)}
5
6 }
还是通过状态行为函数来实现的。
1 def stackRun: State[Stack, Int] = {2 for {3 _ <- push(13)4 a <- pop5 _ <- setState(List(8,9))6 b <- pop7 s1 <- getState8 } yield (a + b)9 } //> stackRun: => ch6.state.State[ch6.state.Stack,Int]
10
11 val (a, s) =stackRun.run(List(10,11,12)) //> a : Int = 21
12 //| s : ch6.state.Stack = List(9)
我们可以临时将状态设置成List(8,9)。
泛函编程(16)-泛函状态-Functional State相关推荐
- 泛函编程(4)-深入Scala函数类
既然是泛函编程,多了解一下函数自然是免不了的了: 方法(Method)不等于函数(Function) 方法不是函数但可以转化成函数:可以手工转换或者由编译器(compiler)在适当的情况下自动转换. ...
- C++ 泛型编程/模板 泛函编程/Lambda/λ演算
1.泛型编程(C++模板) 其中,Ada, Delpha, Java, C#, Swift 称之为 泛型/generics; ML, Scala和 Haskell 称之为 参数多态/parametri ...
- 设计模式之状态模式(State)摘录
23种GOF设计模式一般分为三大类:创建型模式.结构型模式.行为模式. 创建型模式抽象了实例化过程,它们帮助一个系统独立于如何创建.组合和表示它的那些对象.一个类创建型模式使用继承改变被实例化的类,而 ...
- 线程的属性 —— 分离的状态(detached state)、栈地址(stack address)、栈大小(stack size)
参考:(四十二)线程--线程属性 作者:FadeFarAway 发布时间:2017-01-17 14:09:55 网址:https://blog.csdn.net/FadeFarAway/articl ...
- android 状态模式,Android编程设计模式之状态模式详解
本文实例讲述了Android编程设计模式之状态模式.分享给大家供大家参考,具体如下: 一.介绍 状态模式中的行为是由状态来决定的,不同的状态下有不同的行为.状态模式和策略模式的结构几乎完全一样,但它们 ...
- C++设计模式之状态模式(state)(行为型)
一 定义 状态模式:允许一个对象在其内部状态改变时改变它的行为.对象看起来似乎修改了它的类. 状态模式主要解决的是当控制一个对象状态转换的条件表达式过于复杂的情况.把不同状态的操作分散到不同的状态对象 ...
- 【React系列】状态(State)和生命周期
在上一篇中写过,组件可以分为函数式组件和类组件,并且更新组件的方法也给出了通过传入ReactDOM.render()方法进行更新.但是这种方式并不能很好地进行封装成独立功能的组件,一些操作会由外部进行 ...
- 分享一套超棒的iOS “空状态” (empty state) 界面UI设计
日期:2013-2-1 来源:GBin1.com 大家在程序开发或者界面设计中常常会遇到这样一些情况: 404 error 内容未找到 账户余额不够 文件没有找到 等等 这 些典型的属于empty ...
- 结合泛函极值_泛函极值及变分法教程.doc
PAGE PAGE 53 第二章 泛函极值及变分法(补充内容)2.1 变分的基本概念2.1.1 泛函和变分泛函是一种广义的函数,是指对于某一类函数{y(x)}中的每一个函数y(x),变量J有一值与之对 ...
- php 状态模式,PHP设计模式(十九)—状态模式 (State Pattern)
状态模式 (State Pattern) :允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类.其别名为状态对象(Objects for States) (一)为什么需要状态模式 ...
最新文章
- each 数据获取attr_调用高德POI数据,带你玩转长沙
- 105从前序与中序遍历序列构造二叉树 106 从中序与后序遍历序列构造二叉树 (递归 + 哈希)
- 虚拟机使用的是此版本 VMware Workstation 不支持的硬件版本。 模块“Upgrade”启动失败。 未能启动虚拟机(修改.vmx文件virtualHW.version = “xx“字段)
- 在Outlook中设置类似Foxmail带日期的签名
- oracle提升,Oracle特权提升
- 工作中如何设计秒杀场景
- 数据分析与挖掘中常用Python库的介绍与实践案例
- iOS开发经验总结(一)
- Selenium爬虫 -- 图片视频的src绝对地址链接分析
- groovy-实现接口
- Oracle rac11g 安装报INS41112
- linux创建2g文件,嵌入式 创建一个2G的空文件(Linux命令dd)
- 【技术教程】如何使用OBS推流到EasyDSS平台实现同屏播放?
- 浩辰cad电气2021 安装教程
- 红绿灯代码 摘抄抖音 渡一前端的
- pandas数据处理-----(一)
- 项目管理中的成本计算
- 二见钟情之个人重构的心路历程
- 两万字长文总结,梳理 Java 入门进阶那些事(推荐收藏)
- 线程同步的注解:@ThreadSafe、@Immutable、@NotThreadSafe、@GuardedBy
热门文章
- python之路--内置函数03
- 【传输文件】文件传输协议FTP、SFTP和SCP
- Android中FTP服务器、客户端搭建以及SwiFTP、ftp4j介绍
- CH BR8(小学生放假了-clock()/CLOCKS_PER_SEC-斜率优化常错集锦)
- jQuery1.6性能评测
- 采用GDI生成Code39条形码
- MESI--CPU缓存一致性协议
- jstorm的acker实现
- python optimize_Python的五大常用库——numpy,pandas,matplotlib等
- 可有可无的Mysql工作技巧 3 -- 工作中用到的理论范式,工具,建模经验