Scheme中循环定义的问题

时间:2010-05-13 17:42:17

标签: scheme sicp guile

我目前正在使用Guile作为练习的主要语言来完成SICP。我在第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)

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

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

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

3 个答案:

答案 0 :(得分:2)

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

  

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

您可以将此视为解释器收集所有DEFINES并以随机顺序在身体之前进行评估。因为订单是随机的,所以如果您希望它起作用,则不会存在任何相互依赖性。

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

您必须编写代码,以便一个定义非常明确地在另一个定义的范围内,例如嵌套LET:

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

答案 1 :(得分:1)

在REPL中逐个评估定义以及将它们放在solve内时发生的情况之间的关键区别在于,在第一种情况下,它们是按顺序评估的,因此y (stream-map <some-function> y)引用的表达式已经在范围内,而对于内部定义或letrec,它尚不可用。

有趣的是,麻省理工学院计划,我在通过SICP时使用,当时没有这样的问题,仍然对待letrec和内部定义不同:

;; 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)

我不确定最初的“算法语言方案的修订报告”或R2RS,但至少从内部定义上的R3RS应该等同于letrec。显然,麻省理工学院环境的这种独特性影响了这本书......或者也许是另一种方式。

答案 2 :(得分:0)

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

  (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。