使用 Let 创建局部变量
讲解 Racket 中 let 特殊形式的用法,它与 lambda 调用的等价关系,以及如何用它创建局部变量。
局部变量(local variable)是只存在于局部环境(local environment)中的变量。看一个例子:
(define (foo x)
(define a 5)
(+ x a))
这里的局部环境是由函数 foo 创建的环境(environment),局部变量是 a。注意,x 不是局部变量——尽管它在 foo 外面同样无法访问,但它的正式名称是形参(parameter)。
let 简介
特殊形式 let 本质上就是一次调用方式不同的 lambda 函数调用。例如,看下面这个 lambda 函数调用:
-> ((lambda (x y z) (+ x y x z)) 1 2 3)
7
它等价于下面这个 let 语句:
-> (let ((x 1) (y 2) (z 3)) (+ x y x z))
7
这什么时候会有用呢?答案只有两个字:局部变量。我们很少会单纯为了调用一个 lambda 函数而使用 let 语句;相反,我们用它在函数内部创建局部变量。
示例:多项式
假设我们想用 Racket 计算下面这个多项式,其中 x 和 y 为任意给定值:
把这个难看的多项式原样改写成一个难看的过程:
(define (f x y)
(+ (* x (+ 1 (square (* x y)))) (* y (- 1 y)) (* (+ 1 (* x y)) (- 1 y))))
啧。不过,我们可以做一些代换:
于是得到:
好吧,我承认这样似乎好些了。用 Racket 写的话,我们可以定义一个辅助函数 f-helper,这样就可以使用代换了:
(define (f x y)
(define (f-helper a b)
(+ (* x (square a))
(* y b)
(* a b)))
(f-helper (+ 1 (* x y))
(- 1 y)))
花一分钟确认一下:这确实和前面 f 的定义做的是同一件事。如我们在上一节所学,我们其实并不需要在 f 内部额外定义一个函数,可以用 lambda 代替:
(define (f x y)
((lambda (a b)
(+ (* x (square a))
(* y b)
(* a b)))
(+ 1 (* x y))
(- 1 y)))
可惜的是,即便经过这番代换和重组,代码还是有点乱。这时候就该 let 登场了!
(define (f x y)
(let ((a (+ 1 (* x y)))
(b (- 1 y)))
(+ (* x (square a)) (* y b) (* a b))))
终于,我们得到了最初那个多项式函数 f 的一个更可读的版本。我们可以清楚地看到:先给 a 和 b 赋值,再把它们代入 let 语句的主体。
let:一般形式
let 语句的一般结构是:
(let ((<var1> <exp1>)
(<var2> <exp2>)
...
(<varn> <expn>))
<body>)
请记住,从本质上说,它无非是一次 lambda 调用。上面的结构等价于:
((lambda (<var1> <var2> ... <varn>) <body>)
<exp1> <exp2> ... <expn> )
在 Racket 解释器中试试这些表达式(以及更多表达式!)。
(注意:分号表示注释,Racket 会忽略分号之后该行的剩余内容。)
(define y 10)
(let ((y 0)) y) ;; 注意,let 会覆盖全局变量
(let ((x 10)
(z x))
z) ;; 这行会报错,翻译成 lambda 看看为什么
(let ((a 1))
(let ((a 2))
(let ((a 3))
a))) ;; 嵌套 let 是合法的
(let ((test 'wait-what?))
5)
test ;; let 只在其主体内部绑定变量
(let ((a 1))
(+ a (let ((a 2))
(+ a (let ((a 3))
a))))) ;; 挑战:先猜猜最后这个表达式返回什么,再用解释器验证