理解 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)))
我很难理解 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)))