2

我目前正在通过 SICP 使用 Guile 作为练习的主要语言。在执行第 3.5 章的练习时,我发现了一个奇怪的行为。我已经在各种平台上使用 Guile 1.4、Guile 1.8.6 和 Guile 1.8.7 重现了这种行为,并且确信它不是特定于我的设置。

此代码工作正常(并计算 e):

  (define y (integral (delay dy) 1 0.001))
  (define dy (stream-map (lambda (x) x) y))
  (stream-ref y 1000)

以下代码给出相同的结果:

  (define (solve f y0 dt)
    (define y (integral (delay dy) y0 dt))
    (define dy (stream-map f y))
    y)
  (stream-ref (solve (lambda (x) x) 1 0.001) 1000)

但它会产生错误消息:

standard input:7:14: While evaluating arguments to stream-map in expression (stream-map f y):
standard input:7:14: Unbound variable:
y ABORT: (unbound-variable)

因此,当嵌入到过程定义中时, (define y ...) 不起作用,而在 REPL 的全局环境中的过程之外它可以正常工作。

我在这里做错了什么?如有必要,我也可以发布辅助代码(即积分、流图等的定义)。除了 cons-stream 的系统相关代码之外,它们都在书中。我自己对 Guile 的 cons-stream 实现如下:

(define-macro (cons-stream a b)
  `(cons ,a (delay ,b)))
4

3 回答 3

2

您不能拥有相互依赖的内部 DEFINE;语言规范明确说明了这一点(R5RS 5.2.2):

...必须可以评估主体中每个内部定义的每个表达式,而无需分配或引用任何正在定义的变量的值。

您可以将其视为解释器正在收集所有 DEFINES 并在正文之前以随机顺序对它们进行评估。因为顺序是随机的,所以如果您希望它起作用,就不会有任何相互依赖关系。

SOLVE 定义 (#71) 甚至附有一个脚注,说明它不适用于所有方案。

您必须编写代码,以便一个定义非常清楚地在另一个定义的范围内,就像嵌套的 LET 一样:

(定义(求解 f y0 dt)
  (让((y(积分(延迟dy)y0 dt)))
    (let ((dy (stream-map fy)))
      y)))
于 2010-05-14T10:20:48.050 回答
1

The key difference between what happens when you evaluate the definitions one by one at the REPL and when you place them inside solve is that in the first case, they are evaluated sequentially, thus the y the expression (stream-map <some-function> y) refers to is already in scope, whereas with internal definitions or letrec, it is not yet available.

Funnily enough, MIT Scheme, which I used when going through SICP, had no such problem back then and still treats letrec and internal defines differently:

;; this is an error
(letrec ((xs '(1 2 3)) (ys (map (lambda (x) (+ x 1)) xs))) ys)

;; this is still an error (and is treated as such by Guile),
;; yet evaluates to (2 3 4) in MIT Scheme
(let () (define xs '(1 2 3)) (define ys (map (lambda (x) (+ x 1)) xs)) ys)

I'm not sure about the original "Revised Report On Algorithmic Language Scheme" or R2RS, but at least from R3RS on internal defines were supposed to be equivalent to letrec. Apparently this peculiarity of MIT's environment influenced the book... or perhaps it's the other way around.

于 2010-05-13T21:08:26.310 回答
0

按照评论中的想法(参考 R5RS 4.2.2 中的引用),我现在将“ y”和“ dy”的定义包装到(lambda () ...)s 中:

  (define (solve f y0 dt)
    (define (y) (integral (delay (dy)) y0 dt))
    (define (dy) (stream-map f (y)))
    (y))

这确保<init>可以在不引用循环定义变量的情况下评估每个定义的部分,因为定义是过程而不是像原始情况那样具有其他变量的表达式。

现在代码肯定要慢得多(因为函数将被递归包装)并且堆栈大小需要增加,但以下工作并产生正确的结果:

  (debug-set! stack 2000000)
  (stream-ref (solve (lambda (x) x) 1 0.001) 1000)

通过类似的修改,Michał 的示例代码只要定义了过程而不是变量就可以工作:

  (let ()
    (define (xs) '(1 2 3))
    (define (ys) (map (lambda (x) (+ x 1)) (xs)))
    (ys))

适用于 Guile 1.8.6。

于 2010-05-14T21:32:16.343 回答