我目前正在使用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)))
答案 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。