章节目录

从名字开始:变量和 `let`

本节阅读量:

写第二章代码之前,先把语言范围说清楚。

第一章的表达式只有两种:

1
2
整数
加法

第二章增加两种:

1
2
变量引用
let 绑定

也就是说,本章仍然是“一个程序就是一个表达式”,只是表达式种类更多了。

先区分三个会反复出现的词:

1
2
3
绑定    把一个名字和一个值联系起来
引用    在表达式里使用这个名字
作用域  这条绑定能够被引用的源码范围

变量引用

变量是源码里的名字,例如:

1
2
3
x
answer
total

变量引用本身不保存值。它只是说:

1
到当前环境里找这个名字。

这个程序可以得到 40:

1
(let x 40 x)

因为 body 里的 x 可以看到外层 let 建立的绑定。

但这个程序会报错:

1
x

语法上它是一个合法变量引用;求值或编译时才会发现没有任何地方绑定了 x。

let 表达式

本章的 let 写成:

1
(let x 40 (+ x 2))

它由六个小块组成:

1
2
3
4
5
6
(       左括号
let     特殊形式
x       要绑定的名字
40      value,先求出这个值
(+ x 2) body,在这里使用 x
)       右括号

可以读成:

1
在 body 里,让 x 表示 value 的结果。

x 的作用域只覆盖 body,不覆盖 value,也不会延伸到整个 let 外面:

1
2
(let x [value] [body])
        旧环境   旧环境加上 x

换句话说,求 value 时新 x 还不存在;只有进入 body,引用 x 才会找到这次绑定。这种由源码嵌套结构决定名字含义的规则,叫词法作用域。

所以:

1
(let x 40 (+ x 2))

结果是:

1
42

value 先在旧环境里求值

let 的 value 也可以是表达式:

1
(let x (+ 20 20) (+ x 2))

先算 value:

1
(+ 20 20) -> 40

再用扩展后的环境求 body:

1
(+ x 2) -> 42

这里有一个容易写错的规则:

1
(let x 1 (let x (+ x 1) x))

内层 let 的 value 是:

1
(+ x 1)

这个 value 要在旧环境里求值,所以这里的 x 仍然是外层的 1。算出 2 以后,内层 body 里的 x 才绑定到新的 2。

作用域也不会越过 let 的 body。下面程序左边的 x 有绑定,右边的 x 却没有:

1
(+ (let x 40 x) x)

因此整段程序会在处理最后一个 x 时报告未绑定变量。

同名遮蔽

如果内层又绑定同名变量,内层绑定会暂时遮住外层绑定:

1
(let x 10 (+ (let x 32 x) x))

内层:

1
(let x 32 x)

结果是 32。

离开内层以后,外层 x 仍然是 10。所以整个程序是:

1
32 + 10 = 42

这叫 shadowing,同名遮蔽。它不是修改外层变量,而是在更小的范围里建立一个新的同名绑定。第二章的绑定都是不可变的:语言里还没有赋值操作,建立绑定以后不能把其中的值改掉。

写成规则

第二章的表达式规则是:

1
2
3
4
5
6
7
expr ::= integer
       | identifier
       | "(" "+" expr expr ")"
       | "(" "let" identifier expr expr ")"

integer    ::= "-"? digit+
identifier ::= letter+

本章把普通 identifier 规则定得很简单:

1
一个或多个英文字母

所以这些名字都可以作为普通变量名:

1
2
3
x
answer
total

x1、total-1、done? 这类名字暂时不支持。+ 仍然是加法操作符,不是普通变量名。

合法和非法

合法程序:

1
2
3
4
5
6
7
42
-7
x
(+ 40 2)
(+ (+ 1 2) (+ 3 4))
(let x 40 (+ x 2))
(let x 10 (+ (let x 32 x) x))

其中 x 只是语法合法;如果没有绑定,运行时仍然会报错。

非法程序和原因可以一一对照:

程序 为什么不合法
(let x) 缺少 value 和 body
(let x 1) 缺少 body
(let 1 40 1) 绑定名不是 identifier
(+ 1) 加法缺少右边表达式
(+ 1 2 3) 加法后面多出一个表达式
(let x 1 2 3) body 后面还有多余内容

现在已经知道第二章什么文本算合法。下一节先看 AST 怎样表示变量和 let。


2.0 无名,天地之始;有名,万物之母

上一节

2.2 AST:保存变量和绑定结构

下一节