这篇文章是为了熟悉,函数式编程中的消息传递
安装
https://github.com/mcandre/gst-win
下载安装,然后打开 cmd(不能使用 git bash,原因未知),输入 gst 即可开始 Smalltalk 之旅
语法
1. (1 + 2) * 3 // 9
2. 'Hello, world' printNl //'Hello, world' (必须是单引号)
3. Array new: 20 (创建一个数组:长度20)
4. x := Array new: 20 (创建长度20的数组,并赋值给x)!
5. x at: 1 put: 99 (改变x的数组第一项,值为99)
6. (x at: 1) + 1 (把x里面第一项取出来,加上1,x本身不变)
7. x := Set new (创建一个Set)
8. x add: 5. x add: 7. x add: 'foo' (往Set里添加) x add: 5; add: 7; add: 'foo' (简写)
9. x remove: 5 (删除其中的一条)
10. x includes: 5 (是否存在这条)
11. y := Dictionary new (创建一个字典)
12. y at: 'One' put: 1 (往y的这个字典里面放1)
之前我一直搞不懂为什么有些人说 person.cut('xxx') 是
- 给 persom 对象发送了一个 cut 消息
- person 对象会响应这个消息
我们学js的时候理解,这就是一个函数调用,不懂为什么会这么说.
但是,改成 Smalltalk 就理解了
person cut: 'xxx' (给person传递一个消息,切掉xxx)person cut: 'xxx'; cut: 'hands' (给person传递多个消息)
面向对象的核心就是对象与对象之间交互。
- 对象维护自己的状态和生命周期
- 每个对象独立
- 对象和对象直接通过
消息传递
来工作
现在想想这个名字还是很有意思的Smalltalk:说小话
,好像是上课传纸条
面向对象与函数式的关系
请带着如下问题来看下面的文章
- 面向对象和函数式是对立的(不可融合的)吗? //不对立
- 两者的优缺点是什么?
通过一个取钱的程序来看上面的问题
// 创建一个 money变量 和 take函数(set! set是设置这个值,!代表当前可以使用set)(define money 100)(define (take n) (set! money (- money n)) money)
可以将 money 改为局部变量
(define taker (let (money 100) (lambda (n) (set! money (- money n)) money) )(taker 25)75(taker 25)50
或者
(define (taker money) (lambda (n) (set! money (- money n)) money)(define taker1 (taker 100))(define taker2 (taker 100))(taker1 50)50(taker2 70)30
- 同样一个函数,每次执行的结果却不一样。
- 闭包可以存状态,不同的函数有各自的状态
函数式也可以做成消息传递风格
使用消息传递风格就可以构造 account 对象了,account 对象可以响应 withdraw(取钱)和 deposit(存钱)消息:
// 函数money内部返回的是dispatch函数let makeAccount = money => { let take = (n) => { money = money - n return money } let save = (n) => { money = money + n return money } let dispatch = (m) => { return ( m === 'take' ? take : m === 'save' ? save : new Error('unknown request')) } return dispatch}
接下来是使用 makeAccount 创造两个 account 对象(其实是过程):
let account1 = makeAccount(100)account1('take')(70) // 30account1('save')(50) // 80
写成 Lisp 其实更像是消息传递
((account1 'withdraw) 50)((account1 'deposit ) 50)
以上只是利用『分派』模式来模拟对象,函数式编程有完整的面向对象体系,大家有兴趣可以自行了解。
赋值的利弊
引用原文的一句话:
将赋值引进程序设计语言,将会使我们陷入许多困难概念的丛林中。
但是它就没有好处吗?
好处: 每个对象可以存储自己的状态,封装的更彻底,不需要关心数据会怎么变,因为这个对象会维护这个数据
与所有状态都必须显示地操作和传递额外参数的方式相比,通过引进赋值以及将状态隐藏在局部变量中的技术,能让我们以一种更**模块化**的方式构造系统。
但是这本书马上又加了一句话:
可惜的是,我们很快就会发现,事情并不是这么简单。
赋值的代价
1. 赋值操作使我们可以去模拟带有局部状态的对象,但是,这是有代价的,它是我们的程序设计语言不能再用代换模型来解释了。进一步说,任何具有「漂亮」数学性质的简单模型,都不可能继续适合作为处理对象和赋值的框架了。 2. 只要我们不使用赋值,一个「过程/函数」接收同样的参数一定会产生同样的结果,因此就可以认为这个「过程/函数」是在计算「数学函数」。 3. 不用任何赋值的程序设计称为函数式程序设计。
示例
pager 组件的页码生成过程 看代码的时候一定要考虑有没有赋值(一个变量第一次出现等于号是定义,当被定义的改变叫做赋值)
赋值的本质
1. 如果没有赋值,money 只不过就是一个值的名字; 2. 如果有了赋值,money 就是一个容器,可以保存不同的值。3. 这带来的问题很多。广泛采用赋值的程序设计叫做『命令式/指令式』程序设计。4. 命令式程序在遇到『并发』『克隆』等问题时经常很令人头疼。5. 举例说明一下。
函数式编程的特点
- 数学!(公理化和可证明)
- 更加强调程序执行的结果而非执行的过程
- 函数是一等公民(函数可以作为参数--高阶函数)
- 纯函数,拒绝副作用(也就是赋值)
- 不可变数据
- 数据即代码,代码即数据(本课没有涉及)
- 引用透明(本课没有涉及)