这篇文章是为了熟悉,函数式编程中的消息传递
安装
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. 举例说明一下。
函数式编程的特点
- 数学!(公理化和可证明)
- 更加强调程序执行的结果而非执行的过程
- 函数是一等公民(函数可以作为参数--高阶函数)
- 纯函数,拒绝副作用(也就是赋值)
- 不可变数据
- 数据即代码,代码即数据(本课没有涉及)
- 引用透明(本课没有涉及)
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。