【发布时间】:2013-02-06 20:25:50
【问题描述】:
谁能简单解释一下区别?我认为我没有从我咨询过的教科书/网站中理解这个概念。
【问题讨论】:
-
@DavidPfeffer - 似乎不是骗子。那个是询问嵌套
lets 和let*s 的非常具体的交互,而这个询问的是一般概述。 -
简单地混淆了机器执行的人类解释:o
谁能简单解释一下区别?我认为我没有从我咨询过的教科书/网站中理解这个概念。
【问题讨论】:
lets 和let*s 的非常具体的交互,而这个询问的是一般概述。
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* 开头?
let* 在精神上被认为是“必要的”,let - 声明性的。它在概念上是并行的,因为所有 RHS 都在相同的环境(外部环境)中进行评估。在 let 的初始化中查看顺序的唯一方法是 init 表达式是否改变结构,这在“声明式”范式下是不受欢迎的。所以如果我们不做突变,它是,对我们来说,并行,即我们不需要关心初始化的时间,就像我们对let*所做的那样。声明式范式认为,对确切时间的关注越少越好。
如果您使用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。
【讨论】:
let 的文档说“第一种形式评估 val-exprs 从左到右,...”,因此不清楚它们是并行评估的。
val-exprs,为每个 id 创建一个新位置,并将值放入位置” - 意思是,首先对它们进行评估并收集结果值,并且仅为每个 id 创建 然后 个新位置,并将每个值放入它的位置。如果 val-exprs 之一改变了后续存储访问的存储(即数据,如列表或结构),您仍然可以看到顺序。
let*。我看不出总是使用let* 的不利之处,那么为什么这种令人困惑的奇怪现象仍然存在呢?保留区别是否允许表达任何新内容?
let 您可以按任何顺序执行分配,甚至可以并行执行(更符合函数式编程的处理方式),而 let* 强制执行评估顺序并创建一个依赖于前一个变量的值(一种非常程序化的编程方式)。