从名字开始:变量和 `let`
本节阅读量:写第二章代码之前,先把语言范围说清楚。
第一章的表达式只有两种:
|
|
第二章增加两种:
|
|
也就是说,本章仍然是“一个程序就是一个表达式”,只是表达式种类更多了。
先区分三个会反复出现的词:
|
|
变量引用
变量是源码里的名字,例如:
|
|
变量引用本身不保存值。它只是说:
|
|
这个程序可以得到 40:
|
|
因为 body 里的 x 可以看到外层 let 建立的绑定。
但这个程序会报错:
|
|
语法上它是一个合法变量引用;求值或编译时才会发现没有任何地方绑定了 x。
let 表达式
本章的 let 写成:
|
|
它由六个小块组成:
|
|
可以读成:
|
|
x 的作用域只覆盖 body,不覆盖 value,也不会延伸到整个 let 外面:
|
|
换句话说,求 value 时新 x 还不存在;只有进入 body,引用 x 才会找到这次绑定。这种由源码嵌套结构决定名字含义的规则,叫词法作用域。
所以:
|
|
结果是:
|
|
value 先在旧环境里求值
let 的 value 也可以是表达式:
|
|
先算 value:
|
|
再用扩展后的环境求 body:
|
|
这里有一个容易写错的规则:
|
|
内层 let 的 value 是:
|
|
这个 value 要在旧环境里求值,所以这里的 x 仍然是外层的 1。算出 2 以后,内层 body 里的 x 才绑定到新的 2。
作用域也不会越过 let 的 body。下面程序左边的 x 有绑定,右边的 x 却没有:
|
|
因此整段程序会在处理最后一个 x 时报告未绑定变量。
同名遮蔽
如果内层又绑定同名变量,内层绑定会暂时遮住外层绑定:
|
|
内层:
|
|
结果是 32。
离开内层以后,外层 x 仍然是 10。所以整个程序是:
|
|
这叫 shadowing,同名遮蔽。它不是修改外层变量,而是在更小的范围里建立一个新的同名绑定。第二章的绑定都是不可变的:语言里还没有赋值操作,建立绑定以后不能把其中的值改掉。
写成规则
第二章的表达式规则是:
|
|
本章把普通 identifier 规则定得很简单:
|
|
所以这些名字都可以作为普通变量名:
|
|
x1、total-1、done? 这类名字暂时不支持。+ 仍然是加法操作符,不是普通变量名。
合法和非法
合法程序:
|
|
其中 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。