理解 let 上的括号

Understanding parentheticals on let

我很难理解 let 与其他一些语句的语法。例如,"normal" 语句有一个括号:

(+ 2 2)
 = 4   

然而 let 语句有两个:

(let ((x 2)) (+ x 2))
 = 4                                                                                                                         

为什么会这样?我发现很难记住要在各个项目周围加上多少括号。

请记住,let 允许您绑定多个变量。每个变量绑定都是 (variable value) 的形式,您将所有绑定收集到一个列表中。所以一般形式看起来像

(let ((var1 value1)
      (var2 value2)
      (var3 value3)
      ...)
  body)

这就是 x 2 周围有两个括号的原因 -- 内括号用于特定绑定,外括号用于所有绑定的列表。这只是令人困惑,因为您只绑定了一个变量,多个变量变得更加清晰。

首先,请注意 let 语法包含两部分,这两部分都可以有零个或多个元素。它绑定零个或多个变量, 评估零个或多个形式。

所有这些 Lisp 形式都会产生一个问题:如果元素表示为一个平面列表,就会出现歧义:我们不知道一个列表在哪里结束,另一个列表从哪里开始!

(let <var0> <var1> ... <form0> <form1> ...)

例如,假设我们有这个:

(let (a 1) (b 2) (print a) (list b))

什么是(print a):变量print是否绑定到a?还是要评价form0

因此,像这样的 Lisp 构造几乎总是设计成两个列表之一是单个 object,或者可能两者都是。换句话说:这些可能性之一:

 (let <var0> <var1> ... (<form0> <form1> ...))

 (let (<var0> <var1> ...) (<form0> <form1> ...))

 (let (<var0> <var1> ...) <form0> <form1> ...)

传统的Lisp在let的设计中沿用了上面的第三种思路。这个想法的好处是可以在解释器、编译器或任何处理代码的代码中轻松有效地访问表单的各个部分。给定 object L 表示 let 语法,变量很容易检索为 (cadr L) 和 body 形式为 (cddr L).

现在,在这个设计选择中,仍然有一点设计自由度。变量可以遵循类似于 属性 列表的结构:

(let (a 1 b 2 c 3) ...)

或者可以将它们括起来:

(let ((a 1) (b 2) (c 3)) ...)

第二种形式是传统形式。在 Paul Graham 设计的 Lisp 的 Arc 方言中,出现了前一种语法。

繁体形式括号较多。但是,它允许省略初始化形式:也就是说,如果希望一个变量的初始值是nil,而不是写(a nil),你可以写a:

;; These two are equivalent:
(let ((a nil) (b nil) (c)) ...) 
(let (a b c) ...)

这在传统 Lisp 的上下文中很有用 shorthand,它使用符号 nil 表示布尔值 false 和空列表。我们紧凑地定义了三个默认为空列表或假布尔值的变量。

基本上,我们可以将传统的 let 视为主要设计用于绑定一个简单的变量列表,如 (let (a b c) ...) 中默认为 nil。然后,此语法被 扩展 以支持初始值,通过可选地用 (var init) 对替换变量 var,其中 init 是计算的表达式指定其初始值。

无论如何,多亏了宏,您可以拥有任何您想要的绑定语法。在不止一个程序中,我看到一个 let1 宏,它只绑定一个变量,并且没有括号。它是这样使用的:

(let1 x 2 (+ x 2))  ->  4

在 Common Lisp 中,我们可以像这样很容易地定义 let1

(defmacro let1 (var init &rest body)
  `(let ((,var ,init)) ,@body))

如果我们限制 let1 有一个 one-form body,我们就可以写出带有极少括号的表达式;

(let1 x 2 + x 2)   ->  4

那个是:

(defmacro let1 (var init &rest form)
  `(let ((,var ,init)) (,@form)))