let语句的Lisp求值

let语句的Lisp求值,lisp,scheme,interpreter,Lisp,Scheme,Interpreter,我正在编写一个方案解释器,我面临一个有效的let语句,例如: ;; should print 7 (let ((a 4) (b 3)) (let ((a (* a a)) (b (* b b))) (+ a b) (- a b))) 我的解释器只实现Scheme的一个纯粹的功能子集,所以不会有像set!这样的副作用!。在纯函数式语言中,为什么要在上面这样的let语句中允许多个表达式 在编写我的解释器时,除了let中的最后一个表达式之外,

我正在编写一个方案解释器,我面临一个有效的let语句,例如:

;; should print 7
(let ((a 4) (b 3))
    (let ((a (* a a)) 
          (b (* b b)))
       (+ a b)
       (- a b)))
我的解释器只实现Scheme的一个纯粹的功能子集,所以不会有像set!这样的副作用!。在纯函数式语言中,为什么要在上面这样的let语句中允许多个表达式


在编写我的解释器时,除了let中的最后一个表达式之外,我还有什么理由要求值吗?它们似乎永远不会影响最后一条语句的结果。

好的,
let
只是在创建一个绑定,就像
定义一样。没有任何东西像
set那样改变绑定变量会。那么现在,想想你的名字的范围是什么:“(+ab)
a
与你绑定到4的
a`相同吗?(提示:否)

这里真正的要点是,即使在这样的情况下,您也需要正确地进行操作:范围和绑定规则是简单且定义良好的,而这样做看起来很混乱,只是它们的结果。这很方便,因为通过使用
let
进行本地词汇范围的绑定,您可以编写更清晰的程序,即使有不正常的副作用

更新 哦,我漏掉了一点。你是对的,
(+ab)
调用没有持久的效果,但是在一般情况下你不能假设这是真的,你也不能通过单独检查程序文本来确定它是否是真的。(考虑一下:在它的其余部分可能有其他函数代替“
+
”),但是,如果您认为在不评估各种
let
子句的情况下可以得到正确的结果,那么您还不明白它试图做什么。

您是对的(几乎):如果您正在实现Scheme的纯功能子集(即:no
set!
set car!
set cdr!
)然后,除了
let
中的最后一个表达式之外,任何表达式的返回值都将被丢弃,并且由于您保证不会产生副作用,因此无提示地忽略它们是没有危险的

<>强> > 您需要考虑一个小的情况,即前面的表达式是“代码>定义”/代码> s:

(let ((x 3))
  (define y 4)
  (+ x y))
这既是合法的也是功能性的。但是,有一些好消息-在块内(如
let
),您必须将所有
定义在顶部。如中所述,这不被视为合法方案:

(let ((x 3))
  (+ 2 3)
  (define y 4)
  (+ x y))
这意味着在计算块时,您所要做的就是扫描顶部的
define
s,并将它们包装到等效的
letrec
表达式中,然后继续忽略除最后一个表达式之外的所有表达式(然后返回)

edit:在call/cc方面提出了一个很好的观点。如果要在实现中包含continuations,您真的不能对什么时候进行评估做出很多假设。

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

(define (func) (func))

(let ()
  (func) ;; does not return
  1)
在这里,如果您不计算
(func)
,则会得到错误的结果(即1),而您应该得到非终止计算

另一个问题是call/cc(当前延续调用)(是的,它属于函数子集)可用于实际从非尾部位置的计算返回,例如:

(call-with-current-continuation
  (lambda (ret)
    (let ()
      (ret 3)
      4)))
它将返回3而不是4。这仍然是纯功能性的

请注意,
(let()x y z)
相当于单语句形式
(let()(begin x y z))
,因此真正的问题是您是否需要
begin
:)