Scheme 在尾位置编译/计算操作数

Scheme 在尾位置编译/计算操作数,scheme,evaluation,tail-recursion,Scheme,Evaluation,Tail Recursion,我有一个学校的项目,我应该为这个项目为Scheme优化一个编译器/评估器。 任务是尽可能实现尾部调用优化 我知道已知的尾部调用优化,如下所示: (define (f x) <send some rockets into space> (f (+ x 1))) 如果在相同的环境中对操作数求值,我将能够将第一个操作数中定义的x作为第二个操作数传递。然而,这是不可能的 那么,假设每个操作数都可以在尾部位置求值,对吗 “尾部位置”总是相对于某些外部表达式。例如,考虑这一点:

我有一个学校的项目,我应该为这个项目为Scheme优化一个编译器/评估器。 任务是尽可能实现尾部调用优化

我知道已知的尾部调用优化,如下所示:

(define (f x)
    <send some rockets into space>
    (f (+ x 1)))
如果在相同的环境中对操作数求值,我将能够将第一个操作数中定义的
x
作为第二个操作数传递。然而,这是不可能的

那么,假设每个操作数都可以在尾部位置求值,对吗

“尾部位置”总是相对于某些外部表达式。例如,考虑这一点:

(define (norm . args)
  (define (sum-of-squares sum args)
    (if (null? args)
        sum
        (let ((arg (car args)))
          (sum-of-squares (+ sum (* arg arg)) (cdr args)))))
  (sqrt (sum-of-squares 0 args)))
平方和
的递归调用确实位于相对于
平方和
的尾部位置。但它是否处于相对于标准的尾部位置?否,因为来自
平方和
的返回值被发送到
sqrt
,而不是直接发送到
norm
的调用者

分析表达式A相对于外部表达式B是否处于尾部位置的关键是看A的返回值是否由B直接返回,而无需任何进一步的处理

在您的例子中,对于表达式
(f(+12)(begin(define x4)x)5)
(顺便说一下,这实际上是无效的:也许您的意思是
(f(+12)(let()(define x4)x)5)
,而不是子表达式
(+12)
(let()(define x4)x)
,和5相对于
f
,处于尾部位置,因为必须首先收集它们的值,然后将其传递给
f
(这是尾部调用)。

“尾部位置”始终与某些外部表达式相关。例如,考虑这一点:

(define (norm . args)
  (define (sum-of-squares sum args)
    (if (null? args)
        sum
        (let ((arg (car args)))
          (sum-of-squares (+ sum (* arg arg)) (cdr args)))))
  (sqrt (sum-of-squares 0 args)))
平方和
的递归调用确实位于相对于
平方和
的尾部位置。但它是否处于相对于标准的尾部位置?否,因为来自
平方和
的返回值被发送到
sqrt
,而不是直接发送到
norm
的调用者

分析表达式A相对于外部表达式B是否处于尾部位置的关键是看A的返回值是否由B直接返回,而无需任何进一步的处理


在您的例子中,对于表达式
(f(+12)(begin(define x4)x)5)
(顺便说一下,这实际上是无效的:也许您的意思是
(f(+12)(let()(define x4)x)5)
,而不是子表达式
(+12)
(let()(define x4)x)
,和5相对于
f
,处于尾部位置,因为必须首先收集它们的值,然后将其传递给
f
(这是尾部调用)。

应用程序的操作数(op1 op2…)都不处于尾部位置。 对于R5RS方案,您可以在此处看到应用程序在尾部上下文中的位置:


应用程序(op1 op2…)的所有操作数都不在尾部位置。 对于R5RS方案,您可以在此处看到应用程序在尾部上下文中的位置:


所以我终于明白了

在常规R6RS中,永远不能在尾部位置计算操作数,因为R6RS指定计算它们的顺序不严格


然而,在这个方案的自建评估器中,我确实指定了评估它们的顺序。因此,我可以严格定义哪一个操作符是最后一个操作符,并且这个操作符可以在尾部位置进行计算。

所以我最终找到了答案

在常规R6RS中,永远不能在尾部位置计算操作数,因为R6RS指定计算它们的顺序不严格


然而,在这个方案的自建评估器中,我确实指定了评估它们的顺序。因此,我可以严格定义哪一个运算符是最后一个运算符,并且可以在尾部位置对该运算符求值。

您如何将最后一个函数调用参数作为尾部调用求值?记住,在对其求值之后,仍然需要将其值传递给函数调用本身。思考这个问题的一个好方法是,您将如何在CPS(连续传球风格)中实现整个过程?如果且仅当您可以直接(不做任何更改)将延续作为函数调用的延续传递,那么这就是尾部调用。构建的求值器实际上是一个CPS样式的求值器。如果functioncall处于尾随位置,您知道您将为身体评估创建一个新环境。因此,不必还原计算最后一个操作数的最后一个环境。如何将最后一个函数调用参数作为尾部调用进行计算?记住,在对其求值之后,仍然需要将其值传递给函数调用本身。思考这个问题的一个好方法是,您将如何在CPS(连续传球风格)中实现整个过程?如果且仅当您可以直接(不做任何更改)将延续作为函数调用的延续传递,那么这就是尾部调用。构建的求值器实际上是一个CPS样式的求值器。如果functioncall处于尾随位置,您知道您将为身体评估创建一个新环境。因此,不必还原计算最后一个操作数的最后一个环境。
(define (norm . args)
  (define (sum-of-squares sum args)
    (if (null? args)
        sum
        (let ((arg (car args)))
          (sum-of-squares (+ sum (* arg arg)) (cdr args)))))
  (sqrt (sum-of-squares 0 args)))