我很想知道在Scheme中定义多个可以相互调用的词法范围函数。在SICP中工作,我使用块结构生成了以下函数来解决练习1.8(使用牛顿方法计算立方根):
(define (cbrt x)
(define (good-enough? guess prev-guess)
(< (/ (abs (- guess prev-guess))
guess)
0.001))
(define (improve guess)
(/ (+ (/ x (square guess))
(* 2 guess))
3))
(define (cbrt-iter guess prev-guess)
(if (good-enough? guess prev-guess)
guess
(cbrt-iter (improve guess)
guess)))
(cbrt-iter 1.0 0.0))
这很好用,但它让我想知道Scheme(也许是Common Lisp)如何使用词法作用域和let
形式来处理这个场景。我尝试使用let
使用以下kludgy代码实现它:
(define (cbrt x)
(let ((calc-cbrt
(lambda (guess prev-guess)
(let ((good-enough?
(lambda (guess prev-guess)
(< (/ (abs (- guess prev-guess))
guess)
0.001))))
(good-enough? guess prev-guess))
(let ((improve
(lambda (guess)
(/ (+ (/ x (square guess))
(* 2 guess))
3))))
(improve guess))
(let ((cbrt-iter
(lambda (guess prev-guess)
(if (good-enough? guess prev-guess)
guess
(cbrt-iter (improve guess)
guess)))))
(cbrt-iter 1.0 0.0)))))
(calc-cbrt 1.0 0.0)))
我在下面看到的问题是当cbrt-iter
尝试调用good-enough?
程序时。由于good-enough?
过程仅在第一个嵌套let
块的范围内,cbrt-iter
无法访问它。似乎可以通过将cbrt-iter
函数嵌套在let
的封闭good-enough
内来解决这个问题,但这似乎也非常麻烦和笨拙。
在这种情况下,define
形式做什么是不同的? define
表单是否扩展为lambda
表达式而不是“let over lambda”表单(我记得在Little Schemer书中使用((lambda (x) x x) (lambda (y) ...))
形式进行了类似的操作,但我不是确定这将如何工作)。另外,通过比较,Common Lisp如何处理这种情况 - 是否可以使用词法范围defun
?
答案 0 :(得分:1)
首先,您无需引入新程序calc-cbrt
- 您只需拨打calc-iter
即可。
其次,define
和let
的含义完全不同。 Define
将定义安装到本地范围中,如您的示例所示。但是,let
表达式只是lambda
表达式的语法糖(有关详细信息,请参阅SICP第1.3节)。结果(并且如您所述),通过(let (<decl1> ...) <body>)
声明的变量仅在<body>
内可见。因此,(let <decls1> <body1>) (let <decls2> <body2>) ...
的模式不起作用,因为没有一个定义会“存活”到其他范围内。
所以,我们应该这样写:
(define (cbrt x)
(let ((good-enough? (lambda ...))
(improve (lambda ...))
(cbrt-iter (lambda ...)))
(cbrt-iter 1.0 0.0)))
现在,至少,对cbrt-iter
的调用可以看到cbrt-iter
的定义。
但仍有问题。在我们评估(cbrt-iter 1.0 0.0)
时,我们会评估cbrt-iter
的正文,其中guess
和prev-guess
取值1.0和0.0。但是,在cbrt-iter
的正文中,变量improve
和good-enough?
不在范围内。
您可能想要使用嵌套的let
,这通常是一个不错的选择:
(define (cbrt x)
(let ((good-enough? (lambda ...))
(improve (lambda ...)))
(let ((cbrt-iter (lambda ...)))
(cbrt-iter 1.0 0.0))))
问题在于cbrt-iter需要调用自己,但是在内部let
的主体之前它不在范围内!
这里的解决方案是使用letrec
,它类似于let
,但使新绑定在所有声明和正文中都可见:
(define (cbrt x)
(let ((good-enough? (lambda ...))
(improve (lambda ...)))
(letrec ((cbrt-iter (lambda ...)))
(cbrt-iter 1.0 0.0))))
我们甚至可以使用letrec
来创建相互递归的过程,就像使用define
一样。
不幸的是,我需要一些时间来解释letrec
和define
实际上是如何工作的,但这是秘密:它们都在内部使用变异来创建圆形环境数据结构,允许递归。 (还有一种方法可以仅使用lambda
来创建递归,称为 Y组合器,但它相当复杂且效率低下。)
幸运的是,所有这些秘密将在第3章和第4章中公布!
从另一个角度来看,你可以看看Brown University's online PL class,这基本上直接讨论了这个主题(虽然它省略了define
),但我发现SICP更能强迫你理解有时会创建复杂的环境结构。