让作为 lambda 的语法糖

Let as syntactic sugar for lambda

下面的 let 语句是如何根据 lambda 解析的?

(let((a 4)(b 5)) (+ a b))
-->
((lambda (a b) (+ a b)) 4 5)

一般形式是否类似于:

(lambda (key1 key2 ...) <body> (val1 val2 ...))
-->
(let ((key1 val1) (key2 val2) ...) <body>)

使用 let 语法有什么好处?将 key/value 对放在一起是否更容易阅读?

不仅更易读;使用 let 编写代码比编写使用 lambda 来表示 let 的代码更容易。考虑:

(let ((x 2)
      (y 3))
  (let ((u (+ x y))
        (v (* x y)))
    (display "let version:\n")
    (for-each display (list x ", " y "\n" u ", " v "\n"))))
let version:
2, 3
5, 6

以及相同构造的 lambda 版本:

((lambda (x y)
   ((lambda (u v)
       (display "lambda version:\n")
       (for-each display (list x ", " y "\n" u ", " v "\n")))
    (+ x y) (* x y))) 2 3)
lambda version:
2, 3
5, 6

两个版本都有效,但 let 版本通过使用适当的语法表达 intentlambda 版本仅通过其构造的语义表达意图。如果您不知道是否允许表达 let 绑定思想的习语,或者如果您无法通过分析代码拼凑出正在发生的事情,那么 lambda 版本的含义是不透明的.但是 let 版本通过使用专用于 let 绑定的语法清楚地表明了它的意图。

此外,随着 let 绑定场景变​​得更加复杂,使用 let 可以将大部分复杂性转移到语法上。上面的例子是一个简单的嵌套let,在lambda版本中已经很容易出错了。您必须确保圆括号放置正确(实际上我在编写示例代码时设法弄错了一个圆括号),并且最终版本将所有变量标识符放在构造的开头,所有的绑定值在最后。这意味着要了解 lambda 版本,您必须不断引用开头和结尾以了解 body.

中发生的事情

一般来说,lambda表达了一个非常笼统的想法,但是let表达了一个更具体的想法,经常需要。 let 语法的优势在于,它可以让您轻松、简洁和可读地表达更具体的想法,而不是 error-prone 总是通过根据更一般的想法。

(lambda (key1 key2 ...) <body> (val1 val2 ...))
-->
(let ((key1 val1) (key2 val2) ...) <body>)

第一个模式其实是这样的:

((lambda (par1 par2 ...) <body>) arg1 arg2 ...)
-->
(let ((par1 arg1) (par2 arg2) ...) <body>)

函数有参数。 Common Lisp 有位置参数、可选参数、剩余参数和关键字参数。

函数形式有参数形式。对每个参数形式进行评估,然后使用参数值调用函数。参数(局部变量)绑定到参数值并执行主体。

let 的主要好处是可以更轻松地查看哪个参数具有哪种参数形式以及必须提供多少个参数(可能还有哪些)。

想象一下:

((lambda (a b c d e f)
 
 <long body>           ; and yes the body can get long

 )

 1 2 3 4 5 5)

注意,有很多正文超过五行的代码。

let 将论证形式移至顶部,更容易看到评估正文的环境:

(let ((a 1)
      (b 2)
      (c 3)
      (d 4)
      (e 5)
      (d 6))

   <long body>

   )

甚至

(let ((a 1) (b 2) (c 3) (d 4) (e 5) (d 6))

   <long body>

   )