对 Scheme 中 let 和 let* 的区别感到困惑
Posted
技术标签:
【中文标题】对 Scheme 中 let 和 let* 的区别感到困惑【英文标题】:Confused by the difference between let and let* in Scheme 【发布时间】:2013-02-06 20:25:50 【问题描述】:谁能简单解释一下区别?我认为我没有从我咨询过的教科书/网站中理解这个概念。
【问题讨论】:
Scheme Confusing of Let and Let*的可能重复 @DavidPfeffer - 似乎不是骗子。那个是询问嵌套let
s 和let*
s 的非常具体的交互,而这个询问的是一般概述。
简单地混淆了机器执行的人类解释:o
【参考方案1】:
Let
是并行的,(有点;见下文)let*
是连续的。 Let
翻译为
((lambda(a b c) ... body ...)
a-value
b-value
c-value)
但是let*
作为
((lambda(a)
((lambda(b)
((lambda(c) ... body ...)
c-value))
b-value))
a-value)
因此创建了嵌套范围块,其中b-value
表达式可以引用a
,而c-value
表达式可以同时引用b
和a
。 a-value
属于外部范围。这也相当于
(let ((a a-value))
(let ((b b-value))
(let ((c c-value))
... body ... )))
还有 letrec
,允许递归绑定,其中所有变量和表达式属于一个共享范围并且可以相互引用(有一些关于初始化的警告)。它相当于
(let ((a *undefined*) (b *undefined*) (c *undefined*))
(set! a a-value)
(set! b b-value)
(set! c c-value)
... body ... )
(in Racket,也可以在 Scheme 中以 letrec*
的形式使用,因为 R6RS),或者
(let ((a *undefined*) (b *undefined*) (c *undefined*))
(let ((_x_ a-value) (_y_ b-value) (_z_ c-value)) ; unique identifiers
(set! a _x_)
(set! b _y_)
(set! c _z_)
... body ... ))
(in Scheme)。
更新:let
实际上并没有并行评估其值表达式,只是它们都在出现let
表单的相同初始环境中进行评估。从基于lambda
的翻译中也可以清楚地看到这一点:首先在相同的外部环境中评估值表达式每个,然后收集结果值,然后只有 为每个 id 创建新位置,并将值放在每个位置中。如果其中一个值表达式改变了由后续存储访问的存储(即数据,如列表或结构),我们仍然可以看到顺序性。
【讨论】:
如果它实际上不是并行的,那么使用let
的价值是什么?我默认使用它,因为这似乎是其他人使用的约定,然后每次我遇到令人困惑的错误时,我都会将其更改为 let*
这引出了一个问题,为什么不总是使用 let*
开头?
@JosephGarvin let*
在精神上被认为是“必要的”,let
- 声明性的。它在概念上是并行的,因为所有 RHS 都在相同的环境(外部环境)中进行评估。在 let
的初始化中查看顺序的唯一方法是 init 表达式是否改变结构,这在“声明式”范式下是不受欢迎的。所以如果我们不做突变,它是,对我们来说,并行,即我们不需要关心初始化的时间,就像我们对let*
所做的那样。声明式范式认为,对确切时间的关注越少越好。
*如果他们改变了 shared 结构,也就是说。如果结构被封装并且从外部无法观察到其突变,则可以对其进行突变if needed for efficiency sake。【参考方案2】:
如果您使用let
,则不能引用出现在同一let
表达式中的其他 绑定。
例如,这是行不通的:
(let ((x 10)
(y (+ x 6))) ; error! unbound identifier: x
y)
但如果您使用let*
,则可以引用出现在同一let*
表达式中的previous 绑定:
(let* ((x 10)
(y (+ x 6))) ; works fine
y)
=> 16
文档中都是here。
【讨论】:
我在文档中看不清楚(您的链接指向的位置,当前版本 5.3.6),所以我也很困惑。let
的文档说“第一种形式评估 val-exprs
从左到右,...”,因此不清楚它们是并行评估的。
@Alexey 它不会并行评估它们。正如文档所说,“第一个表单从左到右评估 val-exprs
,为每个 id
创建一个新位置,并将值放入位置” - 意思是,首先对它们进行评估并收集结果值,并且仅为每个 id
创建 然后 个新位置,并将每个值放入它的位置。如果 val-exprs
之一改变了后续存储访问的存储(即数据,如列表或结构),您仍然可以看到顺序。
尽管这解释了区别,但它并没有真正解决为什么首先存在区别。我曾经使用过的所有其他语言都只是将每个声明视为一个单独的顺序绑定,它可以依赖于以前的绑定。事实上,在 ALGOL 派生语言中,通常根本没有“同时”声明的概念,它们总是按某种顺序排列,所以就好像你总是在使用 let*
。我看不出总是使用let*
的不利之处,那么为什么这种令人困惑的奇怪现象仍然存在呢?保留区别是否允许表达任何新内容?
@JosephGarvin 和 let
您可以按任何顺序执行分配,甚至可以并行执行(更符合函数式编程的处理方式),而 let*
强制执行评估顺序并创建一个依赖于前一个变量的值(一种非常程序化的编程方式)。
@ÓscarLópez 但据我所知,在实践中,方案实现实际上从未并行评估它们?此外,您将长期需要推断分派到其他线程是否会为自己买单的问题。并且没有什么可以阻止被绑定的表达式接触相同的状态,所以你也不能确定并行执行是安全的。我不得不想象如果让绑定随机重新排序,大多数野外的方案代码都会中断。以上是关于对 Scheme 中 let 和 let* 的区别感到困惑的主要内容,如果未能解决你的问题,请参考以下文章
x 和 x 的集合在 Scheme 中的 let 中不起作用