SICP 第三章的标题是:模块化、对象和状态。我在这一章找到了「函数式程序设计」的定义(见知识点五),哈哈真是一本神书。编程
开篇的一段话十分吸引我,这段话在高层次上说明了面向对象编程的缺点,以及 Rx.js 这种编程范式的优势。数据结构
有一种很是强有力的设计策略,特别适合于构造那些模拟真实物理系统的程序,那就是「基于被模拟的系统的结构去设计程序的结构」。并发
在这一章里,咱们要研究两种特色很鲜明的组织策略,它们源自对于系统结构的两种很是不一样的世界观。第一种策略将注意力集中在「对象」身上,将一个大型系统当作一大批对象,他们的行为可能随着时间的进展而不断变化。第二种策略将注意力集中在流过系统的信息流上,很是像电子工程师观察一个信号处理系统。框架
基于对象的途径和基于流处理的途径,都对程序设计提出了具备重要意义的语言要求。模块化
对于对象途径而言,咱们必须关注计算对象能够怎样变化而又同时保持其标识。这将迫使咱们抛弃老的计算的代换模型,转向更机械式的、理论上也更不容易把握的环境模型。在处理对象、变化和标识时,各类困难的根源都在于咱们须要在这一计算模型中与时间搏斗。若是容许程序并发执行的话,事情就会变得更困难。函数
对于流方式来讲,它特别可以用于松解在咱们的模型中对时间的模拟和计算机求值过程当中的各类事件的发生顺序。咱们将经过延时求值作到这一点。post
考虑一个取钱的函数 withdrawspa
// 初始金额 100
> withdraw(25)
< 75 // 余额 75
> withdraw(25)
< 50
> withdraw(25)
< Error: 余额不足
> withdraw(15)
< 35
复制代码
一样一个函数,每次执行的结果却不同。第一章的代换模型再也不有用了。设计
withdraw 应该如何用「过程」实现呢?记得吗,以前咱们说过也许数据结构均可以用「过程」实现。code
let withdraw = (() => {
let balance = 100
return (amount) => {
if(amount <= balance){
balance = balance - amount
return balance
}else{
throw new Error('余额不足')
}
}
})()
复制代码
这一句 balance = balance - amount 是本书首次出现的对一个量进行赋值的语句(这里 let balance = 100 是「初始化」不是「赋值」, balance 的第二次赋值才是「赋值」)。
Scheme 语言里赋值的语法是
set! balance (- balance amount)
复制代码
赋值被设计成 set! ,足见 Scheme 对赋值的厌恶。
咱们用 makeWithdraw 来建立两个 withdraw,会发现它们两个的状态是互不相干的:
let makeWithdraw = (balance) => (amount) => {
if(amount < balance){
balance = balance - amount
return balance
}else{
throw new Error('余额不足')
}
}
复制代码
下面是两个 withdraw 的行为:
let withdraw1 = makeWithdraw(100)
let withdraw2 = makeWithdraw(100)
withdraw1(50) // 50
withdraw2(70) // 30
复制代码
使用消息传递风格就能够构造 account 对象了,account 对象能够响应 withdraw(取钱)和 deposit(存钱)消息:
let makeAccount = balance => {
let withdraw = (amount) => {
if(amount < balance){
balance = balance - amount
return balance
}else{
throw new Error('余额不足')
}
}
let deposit = (amount) => {
balance = balance + amount
return balance
}
let dispatch = (m) => {
return (
m === 'withdraw' ? withdraw :
m === 'deposit' ? deposit :
new Error('unknown request'))
}
return dispatch
}
复制代码
接下来是使用 makeAccount 创造两个 account 对象(实际上是过程):
let account1 = makeAccount(100)
account1('withdraw')(70) // 30
account1('deposit')(50) // 80
写成 Scheme 其实更像是消息传递
((account1 'withdraw) 50)
((account1 'deposit ) 50)
复制代码
将赋值引进程序设计语言,将会使咱们陷入许多困难概念的丛林中。
可是它就没有好处吗?
与全部状态都必须现实地操做和传递额外参数的方式相比,经过引进赋值以及将状态隐藏在局部变量中的技术,能让咱们以一种更___模块化___的方式构造系统。
可是这本书立刻又加了一句话:
惋惜的是,咱们很快就会发现,事情并非这么简单。
接下来就进入了「赋值的代价」小节:
赋值操做使咱们能够去模拟带有局部状态的对象,可是,这是有代价的,它是咱们的程序设计语言不能再用代换模型来解释了。进一步说,任何具备「漂亮」数学性质的简单模型,都不可能继续适合做为处理对象和赋值的框架了。
只要咱们不使用赋值,一个「过程」接收一样的参数必定会产生一样的结果,所以就能够认为这个「过程」是在计算「数学函数」。
不用任何赋值的程序设计称为函数式程序设计。
未完待续。
第二章的笔记:juejin.im/post/5c02ca…