Scheme 如何用“lambda”重写“let*”?

Scheme 如何用“lambda”重写“let*”?,scheme,lambda,Scheme,Lambda,我理解如何将(let((x v1)(y v2))e重写为((lambda(x y)e)v1 v2)。但是我不太熟悉let* 我们如何在lambda和函数应用程序方面重写(let*((xv1)(yv2)(zv3))e。比如说, (let* ((x v1) (y v2) (z v3)) e) 与 (let ((x v1)) (let ((y v2)) (let ((z v3)) e))) 这有助于您理解let*?:-) 更新:OP正在询问(

我理解如何将
(let((x v1)(y v2))e
重写为
((lambda(x y)e)v1 v2)
。但是我不太熟悉
let*


我们如何在lambda和函数应用程序方面重写
(let*((xv1)(yv2)(zv3))e
。比如说,

(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))
(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))
(let ((x 42))
  (let ((x 10)
        (y (+ x 13)))
    y))

返回55(42+13)。内部
x
的值不用于计算
y
的值;它仅在
let
let
表达式的主体内生效:

(let ((x v1)
      (y v2))
  e)
(let* ((x v1)
       (y v2)
       (z v3))
  e)
等效于以下
lambda
应用程序,注意这里变量可以按任何顺序计算(没有严格的从左到右的顺序),并且一个变量的定义不能引用前面的变量:

((lambda (x y)
   e)
 v1 v2)
另一方面,此
let*
表达式:

(let ((x v1)
      (y v2))
  e)
(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
的定义引用了
x
,只有这样才能起作用:

((lambda (x)
   ((lambda (y)
      (+ x y))
    (+ x 1)))
 1)
最后,这里有两本很棒的在线学习书籍:

什么
让*
扩展到 这:

扩展到:

((lambda (a)
   ((lambda (b)
      (cons a b))
    (* 2 a)))
 1)
这里有一个很好的方法来思考lambda在Scheme中的含义(很好,因为它既简单又准确):它既是程序中某个位置的标签,也是绑定变量的范围。在Scheme中,程序中某个位置的标签(如在其他语言中可以
goto
to,或在机器语言中可以分支到)总是带有绑定变量的范围。您只能通过提供绑定到其范围内绑定的变量的值来“转到”程序中的某个位置

Scheme的
let
是一种表达方式,“我想在这些变量被绑定的地方建立一个作用域,但我不想等到以后再告诉它们的值。我想在这里指定它们的值。”因此,
let
只是一个宏,它生成lambda,然后在那里提供值

如果您希望其中一个变量的值是使用另一个变量的表达式,就像上面用
a
表示
b
一样,则必须在
a
的范围内定义
b
。因此使用了
let*
宏,它在包含前一个变量的范围内定义每个连续变量。因为我们有一堆嵌套的作用域,所以它们是由一堆嵌套的lambda实现的

宏 下面是如何告诉Scheme如何将
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)
在中,您可以在REPL中通过键入
(expand'(let*([a1][b(*2a)])(cons a b))
来处理此问题,并查看结果。以下是我尝试此功能时得到的结果:

(let ([#:a 1]) (let ([#:b (#2%* 2 #:a)]) (#2%cons #:a #:b)))

这与((lambda(x y z)e)v1 v2 v3)有何不同?或者这是无效的?这只是第一种情况(a
let
)。它与第二种情况完全不同,因为在您刚刚发布为注释的代码中,变量可以按任何顺序计算(而不是像a
let*
那样严格按从左到右的顺序),一个变量不能引用itI更新我的答案之前定义的其他变量,请重新阅读;)我也用一个不调用未定义行为的示例更新了我的答案。:-)非常感谢您的解释(和来源)!这帮了大忙!我懂了。所以,如果一个人编码:(let*((x42))(let*((x10)(y(+x13)))y),这是否也会导致23(10+13)?是的,但我鼓励你实际尝试一下。:-)对于那些对这个问题投了否决票的人来说:这是一个关于这个方案的好的、基本的问题,任何初学者都可能希望得到答案。这可能是一个家庭作业问题,但我认为我们应该明确回答这一问题,这样人们就可以很容易地在网上找到它,这是一个非常普遍的问题。如果你同意,请收回你的反对票。谢谢