我了解如何(let ((x v1) (y v2)) e)
重写为((lambda (x y) e) v1 v2)
. 但是我不太熟悉let*
。
我们如何(let* ((x v1) (y v2) (z v3)) e)
根据 lambda 和函数应用程序进行重写?
这个let
表达式:
(let ((x v1)
(y v2))
e)
等效于下面的lambda
应用程序,注意这里的变量可以按任何顺序求值(不强制执行严格的从左到右顺序),并且一个变量的定义不能引用它之前的变量:
((lambda (x y)
e)
v1 v2)
另一方面,这个let*
表达式:
(let* ((x v1)
(y v2)
(z v3))
e)
可以转换为一系列嵌套lambda
的 s,以确保变量的评估顺序与用于定义它们的顺序相同,并且首先定义的变量可以在所有后续定义中引用:
((lambda (x)
((lambda (y)
((lambda (z)
e)
v3))
v2))
v1)
另一个例子:这个代码只有在我们使用第二个转换时才有效:
(let* ((x 1)
(y (+ x 1)))
(+ x y))
可以看到,y
references的定义x
,只有这样才能起作用:
((lambda (x)
((lambda (y)
(+ x y))
(+ x 1)))
1)
最后,这里有两本很棒的学习 Scheme 的在线书籍:
let*
扩展到什么这个:
(let* ([a 1] [b (* 2 a)])
(cons a b))
扩展为:
((lambda (a)
((lambda (b)
(cons a b))
(* 2 a)))
1)
这是一个很好的方式来思考lambda
Scheme 中的含义(很好,因为它既简单又准确):它既是程序中位置的标签,又是绑定变量的范围。在 Scheme 中,程序中某个位置的标签(例如您可以goto
在其他语言中执行的操作或在机器语言中的分支)始终与绑定变量的范围一起使用。您只能通过提供绑定到其范围内的变量的值来“转到”程序中的某个位置。
Schemelet
是一种说法,“我想创建一个绑定这些变量的范围,但我不想等到稍后再告诉它们的值。我想在这里指定它们的值。” 因此,let
它只是一个生成 lambda 然后在那里提供值的宏。
如果您希望其中一个变量的值是使用另一个变量的表达式,就像上面b
表达的方式一样a
,那么b
必须在a
. 因此,let*
宏定义了包含前一个变量的范围内的每个连续变量。由于我们有一堆嵌套的作用域,它们是由一堆嵌套的 lambda 实现的。
以下是如何告诉 Scheme 如何将 a 重写let*
为一堆嵌套的 lambda 和函数应用程序:
(define-syntax let*
(syntax-rules ()
[(__ () body ...)
(begin body ...)]
[(__ ([v e] [v* e*] ...) body ...)
((lambda (v)
(let* ([v* e*] ...)
body ...))
e)]))
(let* ([a 1] [b (* 2 a)])
(cons a b))
=> (1 . 2)
在Chez Scheme中,您可以在 REPL 中通过键入(expand '(let* ([a 1] [b (* 2 a)]) (cons a b))
并查看结果来解决这个问题。这是我尝试时出现的结果:
(let ([#:a 1]) (let ([#:b (#2%* 2 #:a)]) (#2%cons #:a #:b)))
let*
只是嵌套let
实例。例如,
(let* ((x v1)
(y v2)
(z v3))
e)
是相同的
(let ((x v1))
(let ((y v2))
(let ((z v3))
e)))
这对你的理解有帮助let*
吗?:-)
更新:OP 正在询问(在对 Óscar 帖子的评论中)let*
与let
. 这是一个示例:首先,让我们使用let*
:
(let ((x 42))
(let* ((x 10)
(y (+ x 13)))
y))
这将返回 23 (10 + 13)。内部的值x
被使用,外部的值x
被遮蔽。
现在,让我们看看如果我们使用let
而不是会发生什么let*
:
(let ((x 42))
(let ((x 10)
(y (+ x 13)))
y))
这将返回 55 (42 + 13)。内部的值x
不用于计算y
; 它只在let
.