这篇文章是为了熟悉, 函数式编程中的消息传递
安装
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) // 30
account1('save')(50) // 80
写成 Lisp 其实更像是消息传递
((account1 'withdraw) 50)
((account1 'deposit) 50)
以上只是利用『分派』模式来模拟对象,函数式编程有完整的面向对象体系,大家有兴趣可以自行了解。
赋值的利弊
引用原文的一句话:
将赋值引进程序设计语言,将会使我们陷入许多困难概念的丛林中。
但是它就没有好处吗?
好处: 每个对象可以存储自己的状态, 封装的更彻底, 不需要关心数据会怎么变, 因为这个对象会维护这个数据
与所有状态都必须显示地操作和传递额外参数的方式相比,通过引进赋值以及将状态隐藏在局部变量中的技术,能让我们以一种更 ** 模块化 ** 的方式构造系统。
但是这本书马上又加了一句话:
可惜的是,我们很快就会发现,事情并不是这么简单。
赋值的代价
1. 赋值操作使我们可以去模拟带有局部状态的对象,但是,这是有代价的,它是我们的程序设计语言不能再用代换模型来解释了。进一步说,任何具有「漂亮」数学性质的简单模型,都不可能继续适合作为处理对象和赋值的框架了。2. 只要我们不使用赋值,一个「过程 / 函数」接收同样的参数一定会产生同样的结果,因此就可以认为这个「过程 / 函数」是在计算「数学函数」。3. 不用任何赋值的程序设计称为函数式程序设计。
示例
pager 组件的页码生成过程 看代码的时候一定要考虑有没有赋值(一个变量第一次出现等于号是定义, 当被定义的改变叫做赋值)
赋值的本质
1. 如果没有赋值,money 只不过就是一个值的名字;2. 如果有了赋值,money 就是一个容器,可以保存不同的值。3. 这带来的问题很多。广泛采用赋值的程序设计叫做『命令式 / 指令式』程序设计。4. 命令式程序在遇到『并发』『克隆』等问题时经常很令人头疼。5. 举例说明一下。
函数式编程的特点
- 数学!(公理化和可证明)
- 更加强调程序执行的结果而非执行的过程
- 函数是一等公民(函数可以作为参数 – 高阶函数)
- 纯函数,拒绝副作用(也就是赋值)
- 不可变数据
- 数据即代码,代码即数据(本课没有涉及)
- 引用透明(本课没有涉及)