我正在编写一个Scheme解释器,我面临一个有效的let语句,例如:
;; should print 7
(let ((a 4) (b 3))
(let ((a (* a a))
(b (* b b)))
(+ a b)
(- a b)))
我的解释器只实现了Scheme的一个纯函数子集,因此不存在set!等副作用。在纯函数式语言中,为什么要在let语句中允许多个表达式,如上所述?
在编写我的翻译时,除了let中的最后一个表达式,我还有什么理由可以评估它吗?它们似乎永远不会影响最后评估的结果。
答案 0 :(得分:6)
实际上你不能“删除”除最后一个语句之外的所有语句,因为之前的语句可能是非终止的。例如:
(define (func) (func))
(let ()
(func) ;; does not return
1)
如果您将(func)
保留为未评估状态,则会得到错误的结果(即1),而您应该得到非终止计算。
另一个问题是call / cc(call-with-current-continuation)(是的,它属于函数子集)可以用来实际从非尾部计算返回职位,例如:
(call-with-current-continuation
(lambda (ret)
(let ()
(ret 3)
4)))
将返回 3 而不是4.这仍然是纯粹的功能。
请注意,(let () x y z)
等同于单一陈述形式(let () (begin x y z))
,所以真正的问题是如果您需要begin
:)
答案 1 :(得分:2)
你是对的(差不多):如果你正在实现一个纯函数的Scheme子集(即没有set!
,set-car!
,set-cdr!
)那么任何表达式,但是最后一个一个let
将丢弃它们的返回值,并且由于你保证不会有副作用,所以没有危险,默默地忽略它们。
然而,您需要考虑一个小案例,即前面的表达式是define
s:
(let ((x 3))
(define y 4)
(+ x y))
这既合法又实用。但是,有一些好消息 - 在一个区块内(如let
),您必须将所有define
放在顶部。如同,这不被视为合法方案:
(let ((x 3))
(+ 2 3)
(define y 4)
(+ x y))
这意味着在评估一个块时,您所要做的就是扫描顶部的define
并将它们包装到等效的letrec
表达式中,然后继续忽略除最后一个表达式之外的所有表达式(然后你会回来)。
编辑 antti.huima对call / cc提出了一个很好的观点。如果你要在实现中包含continuation,那么你真的不能对什么时候进行评估做出很多假设。
答案 2 :(得分:1)
好的,let
只是创建一个绑定,就像define
一样。那里没有任何改变像set!
那样的绑定变量。现在,想一想你的名字的范围是什么:是你绑定到4的'(+ a b)a
a`的the same as the
? (提示:不。)
这里的真正要点是你需要在这样的简单情况下正确行事:范围和绑定规则简单且定义明确,并且执行类似这样的操作,看起来令人困惑,只是他们的结果。这很方便,因为通过使用let
进行本地词法范围的绑定,即使存在不正常的情况,也可以编写更清晰的程序。
<强>更新强>
哦,我离开了一点。你是对的(+ a b)
调用没有持久的效果,但是在一般情况下你不能认为这是真的,你无法通过单独检查程序文本来确定它是否成立。 (考虑:可能还有其他功能代替“+
”。)但是,如果您认为在不评估各种let
条款的情况下得到正确的结果,则可以使用其他函数。不明白它还在尝试做什么。