我正在阅读这本书Structure and implementation of computer programs,在其中一章中,有一些代码用于计算数字的阶乘:
(define (factorial n)
(fact-iter 1 1 n))
(define (fact-iter product counter max-count)
(if (> counter max-count)
product
(fact-iter (* counter product)
(+ counter 1)
max-count)))
在本书的前面,我了解到我可以在另一个函数中内联函数,如下所示:
(define (factorial n)
(define (fact-iter product counter max-count)
(if (> counter max-count)
product
(fact-iter (* counter product)
(+ counter 1)
max-count)))
(fact-iter 1 1 n))
我知道使用第二种方法fact-iter
将无法在factorial
范围之外访问,但我想知道当我运行第二版factorial
时会发生什么?
是否定义了符号fact-iter
的新本地绑定并创建了一个新函数,或者只在程序编译时创建了一次此绑定?
我来自java背景,这对我来说还不清楚。
答案 0 :(得分:4)
它取决于Scheme实现(其策略将在 SICP 的后续章节中讨论)。从概念上讲,根据您的第二个定义,每次调用factorial
时都会定义/编译一个新函数。但是,一个好的编译器可以转换这个代码,使它更像你的第一个定义。
由于这个结构在Scheme中是如此常见(编写循环的惯用方法是命名 - let
构造,它也可以动态定义函数),因此Scheme编译器应该非常擅长这种优化。事实上,你的例子对于优化器来说很容易处理,因为内部函数的定义实际上并不依赖于外部函数绑定的任何变量,所以它几乎可以原样解除(只有名称可能需要更改)
答案 1 :(得分:2)
每次调用factorial时,新函数都不会编译。功能正式是一段代码和环境;每次通话都可以改变环境。例如:
(define (find x l)
(define (equal-to-x y) (equal? x y))
....)
在上面,每次调用'find'都会产生一个新的函数'equal-to-x'。 'equal-to-x'函数的'environment'引用了另一个范围中定义的变量'x'。但是,一个合适的编译器可能会注意到,从不返回等于x或绑定到超出范围的变量,因此编译器可能“内联”代码 - 从而不需要新的函数(代码+环境)。
在您的代码中,内部定义的事实(第二种情况)的自由引用(+,*和>)都是全局定义的,并且不返回(或绑定)事实 - iter函数。因此,一个足够好的编译器可以内联它。
这是一个不太好的编译器的情况。您可以在'find'的反汇编中看到创建了一个函数/闭包(代码+环境),并在'ex'的反汇编中看到了使用了一个环境引用(以获得'x')。
=> (define find (lambda (x l)
(define ex (lambda (y) (= x y)))
(ex (car l))))
(=>)
=> (sys:disassemble find)
;; Address : 0x00327e90
;; Label : find
;; Constants:
;; 0: #t
;; 1: #[<code> ex 1]
;; 2: (<cons> 6)
;; Code :
;; 0-1: explode 2
;; 2-3: check 2
;; 4-5: get-loc 0
;; 6-8: closure 1, 1
;; 9-10: get-loc 2
;; 11-13: get-loc-res 1, 2
;; 14: cons$car
;; 15-16: call-tail-check 1
;; :
;; Address : 0x0031fb40
;; Label : ex
;; Constants:
;; 0: #t
;; 1: (<number> 1 142 42 154 158)
;; Code :
;; 0-1: explode 1
;; 2-3: check 1
;; 4-6: get-env-res 0, 1
;; 7-9: get-loc-res 0, 1
;; 10: number$=
;; 11-12: return 1
;; :
;; Env :
#[<function> find 2]