5.2 语境 (Context)

另一个我们用来区分表达式的操作符是 let 。它接受一个代码主体,但允许我们在主体内设置新变量:

  1. > (let ((x 7)
  2. (y 2))
  3. (format t "Number")
  4. (+ x y))
  5. Number
  6. 9

一个像是 let 的操作符,创造出一个新的词法语境(lexical context)。在这个语境里有两个新变量,然而在外部语境的变量也因此变得不可视了。

概念上说,一个 let 表达式等同于函数调用。在 2.14 节证明过,函数可以用名字来引用,也可以通过使用一个 lambda 表达式从字面上来引用。由于 lambda 表达式是函数的名字,我们可以像使用函数名那样,把 lambda 表达式作为函数调用的第一个实参:

  1. > ((lambda (x) (+ x 1)) 3)
  2. 4

前述的 let 表达式,实际上等同于:

  1. ((lambda (x y)
  2. (format t "Number")
  3. (+ x y))
  4. 7
  5. 2)

如果有关于 let 的任何问题,应该是如何把责任交给 lambda ,因为进入一个 let 等同于执行一个函数调用。

这个模型清楚的告诉我们,由 let 创造的变量的值,不能依赖其它由同一个 let 所创造的变量。举例来说,如果我们试着:

  1. (let ((x 2)
  2. (y (+ x 1)))
  3. (+ x y))

(+ x 1) 中的 x 不是前一行所设置的值,因为整个表达式等同于:

  1. ((lambda (x y) (+ x y)) 2
  2. (+ x 1))

这里明显看到 (+ x 1) 作为实参传给函数,不能引用函数内的形参 x

所以如果你真的想要新变量的值,依赖同一个表达式所设立的另一个变量?在这个情况下,使用一个变形版本 let*

  1. > (let* ((x 1)
  2. (y (+ x 1)))
  3. (+ x y))
  4. 3

一个 let* 功能上等同于一系列嵌套的 let 。这个特别的例子等同于:

  1. (let ((x 1))
  2. (let ((y (+ x 1)))
  3. (+ x y)))

letlet* 将变量初始值都设为 nilnil 为初始值的变量,不需要依附在列表内:

  1. > (let (x y)
  2. (list x y))
  3. (NIL NIL)

destructuring-bind 宏是通用化的 let 。其接受单一变量,一个模式 (pattern) ── 一个或多个变量所构成的树 ── 并将它们与某个实际的树所对应的部份做绑定。举例来说:

  1. > (destructuring-bind (w (x y) . z) '(a (b c) d e)
  2. (list w x y z))
  3. (A B C (D E))

若给定的树(第二个实参)没有与模式匹配(第一个参数)时,会产生错误。