2.5 Lambda表达式

在表达式 (let ((x (* 3 4))) (+ x x)) 里,变量 x 绑定到值 (* 3 4)。如果我们喜欢,我们可以把它绑定为 (/ 99 11),也可以绑定为(- 2 7)?在每一种情况下,我们都需要一个猜(fixme)有不同的单独的 let 表达式,这很不方便。

我们可以使用 lambda 语法形式来创建一个函数,它有一个参数 x ,并且 body 部分与 let 表达式相同。

(lambda (x) (+ x x)) => #<procedure>

lambda 表达式的通用形式是

(lambda (var ...) body1 body2 ...)

变量 var ... 是一个函数的形式参数,而body1 body2...为函数体。

函数是与数字、字符串、符号或者 pair 一样的对象。它没有任何有意义的打印表示法,所以,本书使用#<procedure>来表示一个函数的值。

使用一个函数最常见的方式是将它应用到一个或多个值上。

((lambda (x) (+ x x)) (* 3 4)) => 24

这与普通的函数调用没有任何区别。函数是表达式(lambda (x) (+ x x))的值,唯一的参数是(* 3 4),即12.与 let 绑定的方式相同,参数值(又叫实参)被绑定到 lambda 表达式里的形式参数上。在这种情况下,x绑定到12,(+ x x)的值是24。因此,将值 12 应用到函数的结果就是 24.

因为函数是一个对象,我们可以将函数绑定为一个变量,并且多次使用它。

(let ((double (lambda (x) (+ x x))))
   (list (double (* 3 4))
         (double (/ 99 11))
         (double (- 2 7))))
=> (24 18 -10)

这里,我们将 double 绑定为一个函数,然后使用这个函数应用到3个不同的值上面。

该函数预期它的实参是一个数字,然后将该实参再传递给+。大多数情况下,实参可以是任意的对象。比如,有一个类似的函数,使用 cons 来代替 +

(let ((double-cons (lambda (x) (cons x x))))
  (double-cons 'a)) => (a . a)

请注意到 doubledouble-cons 之间的相似性,通过增加一个参数,可以将这两个函数合二为一,你不应该对此感到惊讶。

(let ((double-any (lambda (f x) (f x x))))
  (list (double-any + 13)
        (double-any cons 'a))) => (26 (a . a))

这里演示了一个函数可以接受一个以上的参数,而传递给函数的参数本身也可以是一个函数。

当一个 lambda 表达式嵌套在另一个 lambda 或者 let 表达式中,情况变得有趣起来。

(let ((x 'a))
  (let ((f (lambda (y) (list x y))))
    (f 'b))) => (a b)

出现在 lambda 表达式内部的 x 引用了由外部的 let 表达式绑定的变量 x。对 lambda 表达式而言,变量x被称作自由变量. 变量y并非自由变量,它被绑定在 lambda 内部. A variable that occurs free in a lambda expression should be bound, e.g., by an enclosing lambda or let expression, unless the variable is (like the names of primitive procedures) bound outside of the expression, as we discuss in the following section.

当一个函数应用到绑定作用域之外的某个变量会发生什么?比如下面的表达式:

(let ((f (let ((x 'sam))
           (lambda (y z) (list x y z)))))
  (f 'i 'am)) => (sam i am)

答案是, the same bindings that were in effect when the procedure was created are in effect again when the procedure is applied. This is true even if another binding for x is visible where the procedure is applied.

(let ((f (let ((x 'sam))
           (lambda (y z) (list x y z)))))
  (let ((x 'not-sam))
    (f 'i 'am))) => (sam i am)

注:这里演示的是词法闭包。闭包内部的变量 x (值为 'sam) 在函数被创建的时候值就确定了,当在另外的环境中使用闭包时,虽然当前环境中有一个同名的 x(值为 'not-sam),起作用的依然是闭包内部的x

顺便说一下,let表达式只不过是将一个lambda直接应用到一组参数上。例如,下面的两个表达式完全等价。

(let ((x 'a)) (cons x x))  =  ((lambda (x) (cons x x)) 'a)

事实上,let表达式只是一个由lambda来定义的语法扩展。这两者都是核心语法形式。一般来说,任何下面的表达式:

(let ((var expr) ...) body_1 body_2)

等价于:

((lambda (var ...) body_1 body_2)
  expr ...)

如上所述,lambda的一般形式比我们先前看到的形式有点复杂,在正式的参数规范里,(VAR...),不一定是一个固定的列表,甚至连列表成员都可以没有。形式参数可以是下面三种形式中的任意一种:

  • 一个固定的变量列表, (var_1 ... var_n),就如同我们看到过的
  • 一个单独的变量,var_r 或者是
  • 一个不规范的列表, (var_1 ... var_n . var_r).

第一种情况下,必须精确地传递n个实参,而每一个变量(形参)按照参数传递的顺序绑定到对应的实参上。

第二种情况下,可以传递任意下参数,所有的参数作为一个列表被传递进函数,形参var_r就绑定到这个列表上

第三种情况是第一种和第二种形式的混合,必须传递不少于 n 个实参,传递的实参依次与 var_1 ... var_n 绑定,而多于 n 个的实参,作为一个列表绑定到 var_r 上。

第二三种情况中,var_r 有时被称作 rest 参数。

我们用几个小例子来搞清 lambda 表达式的常规语法。

(let ((f (lambda x x)))
  (f 1 2 3 4)) => (1 2 3 4)

(let ((f (lambda x x)))
  (f)) => ()

(let ((g (lambda (x . y) (list x y))))
  (g 1 2 3 4)) => (1 (2 3 4))

(let ((h (lambda (x y . z) (list x y z))))
  (h 'a 'b 'c 'd)) => (a b (c d))

results matching ""

    No results matching ""