let的用处是什么?

3
区分Scheme的let、let*和letrec三种不同的let形式在实践中是否有用?
我目前正在开发一种lisp风格的语言,支持这3种形式,但我发现:
1. 常规的“let”是最低效的形式,必须要转换为立即调用的lambda形式,生成的指令几乎相同。此外,我很少需要使用这种形式。
2. let*(顺序绑定)似乎是最实用和经常使用的形式。此形式可以转换为一系列嵌套的“let”,每个环境存储一个变量。但这又是非常低效的,浪费空间和查找时间。
3. letrec(递归绑定)可以高效地实现,只要没有初始化器表达式引用未绑定的变量。通常情况下,所有初始化器都是lambda表达式且上述条件成立。
问题是:既然letrec可以有效地实现,并且还包含了let*的行为,而常规let并不经常使用,可以将默认的“let”行为设置为当前的“letrec”,并摆脱原始的“let”吗?
2个回答

4
这个 [let*] 表达式可以被翻译为一系列嵌套的"let"表达式,每个环境都存储一个变量。但这样做非常低效,浪费空间和查找时间。
虽然你所说的并不是不正确的,事实上并不需要这样的转换。针对简单的let编译策略就能处理let*的语义,只需要进行简单的修改(可能只需通过传递给公共代码的标志来同时支持两种)。 let*只是改变了作用域规则,这些规则在编译时就已经确定;它主要是关于编译给定变量的init形式时使用哪个编译时环境对象的问题。
编译器可以使用单个环境对象来顺序绑定一个let*的变量,并在编译变量init表单时进行破坏性更新,以便每个连续的init表单看到该环境的更多扩展版本,其中包含越来越多的变量。在此之后,完整的环境可用于生成框架等生成代码。
需要注意的一个问题是let*的扁平环境表示意味着在变量绑定阶段捕获的词法闭包可能会捕获将来对它们不可见的变量。
(let* ((past 42)
       (present (lambda () (do-something-with past)))
       (future (construct-huge-cumbersome-object)))
  ...))

如果这里有一个包含变量past、present和future的编译版本的单个运行时环境对象,那么这意味着lambda必须捕获该环境。这意味着尽管表面上lambda仅“看到”变量past,但因为未来不在作用域内,实际上它已经捕获了future。
因此,只要lambda保持可达性,垃圾回收将认为这个庞大而笨重的对象是可达的。
有一些方法可以解决这个问题,例如从lambda发出的环境引用伴随着某种框架索引,表示“我只引用环境向量的部分,直到索引13”。然后当垃圾回收器遍历这个栅栏引用时,它将仅标记环境向量中指定的部分:0到13号单元格。
关于是否要同时实现let和let*,我怀疑如果Lisp今天从头开始进行“绿地”设计,许多设计师会喜欢选择顺序绑定版本称为let。并行构造将以特殊名称let*提供。你实际需要let是并行的情况比较少。例如,let允许我们重新绑定一对变量符号,使其内容交换;但这很少在应用程序编程中出现。在一些编程语言文化中,变量阴影被完全抵制;例如,GNU C有一个-Wshadow警告来防止它。
请注意,在具有let和let*的ANSI Common Lisp中,函数的可选参数按顺序执行,就像let*一样,并且这是唯一支持的绑定策略!换句话说:
(lambda (required &optional opt1 (opt2 opt1)) ...)

在这里,opt2 的值从调用时候的 opt1 值默认赋值。同时 opt2 的初始化表达式中也包含了 opt1 参数。

此外,在同一 Lisp方言中,常规的 setf 是串行的;如果需要并行赋值,必须使用 psetf,这是两者中更长的名称。

Common Lisp 显然比 let 更多地考虑到序列操作,并将并行设计为非正常的变体。


并行变量的使用帮助我阅读代码,因为它表明引入的变量是独立的。 - coredump
@coredump 我认为这会使代码更难阅读,因为它打破了“查找被引用标识符的定义,只需从文件中该点向后搜索”的规则。在 let 下,对某个 X 的引用不一定会指向文本上最近的定义,而可能是之前的一个。你正在阅读的代码不一定是正确的:如果你有 (let ((a (foo)) (b (+ a (bar))) ...),你知道 ab 是独立的;但这是 意图 吗?也许那个 a 应该指向文本上最近的定义。 - Kaz

1

想一下元编程。如果你的默认let会顺序创建嵌套作用域,那么你必须确保初始化表达式中没有引用错误作用域的名称。使用常规let可以保证这一点。当你生成代码时,对名称作用域的控制非常重要。

Letrec甚至更糟,它引入了非常复杂的作用域规则,很难理解。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接