8

我正在编写一个 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 中的最后一个表达式,我还有什么理由应该评估任何东西?似乎它们永远不会影响评估的最后一个语句的结果。

4

3 回答 3

7

实际上,除了最后一条语句之外,您不能“删除”所有语句,因为前面的语句可能是非终止的。例如:

(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:)

于 2009-03-17T04:13:52.123 回答
2

你是对的(几乎):如果你正在实现 Scheme 的纯函数子集(即 no set!, set-car!, set-cdr!),那么除了 a 中的最后一个之外的任何表达式let都将丢弃它们的返回值,因为你保证没有副作用,默默地忽略它们并没有危险。

但是,您需要考虑一种小情况,即当前面的表达式为defines 时:

(let ((x 3))
  (define y 4)
  (+ x y))

这既合法又实用。但是,有一些好消息 - 在块内(如 a let),您必须将所有defines 放在顶部。如,这不被视为合法方案:

(let ((x 3))
  (+ 2 3)
  (define y 4)
  (+ x y))

这意味着当评估一个块时,您所要做的就是扫描顶部的defines 并将它们包装到等效的letrec表达式中,然后继续忽略除最后一个表达式之外的所有表达式(然后您将返回)。

编辑: antti.huima对 call/cc 提出了很好的观点。如果你要在你的实现中包含延续,你真的不能对什么时候评估事情做出很多假设。

于 2009-03-17T04:08:27.673 回答
1

好的,这let只是创建一个绑定,比如define. 那里没有任何东西可以改变绑定变量set!。所以现在,想想你的名字的范围是什么:你绑定到 4a的 '(+ ab) a` 是什么?the same as the(提示:没有。)

这里真正的要点是,即使在像这样的 hinky 情况下,您也需要正确行事:范围和绑定规则简单且定义明确,而做这样看起来令人困惑的事情只是它们的结果。这很方便,因为通过与 进行本地词法范围绑定let,您可以编写更清晰的程序,即使存在不正当的情况。

更新 哦,我离开了一点。调用没有持久效果是对的(+ a b),但是在一般情况下,您不能假设这是真的,并且您无法通过单独检查程序文本来确定它是否正确。(考虑一下:那里可能有其他函数代替“ +”。)但是,如果您认为在不评估各种let子句的情况下会得到正确的结果,那么您还不明白它要做什么.

于 2009-03-17T03:38:59.087 回答