The book在第1章中定义了块结构,使您可以将define
封装在过程定义中。
例如考虑以下mean-square
定义:
(define (mean-square x y)
(define (square x) (* x x))
(define (average x y) (/ (+ x y) 2))
(average (square x) (square y)))
当我运行(mean-square 2 4)
时,我会正确获得10
。
我的问题是,内部定义(此玩具盒中的square
和average
是否每次运行 我通过解释器调用mean-square
过程?如果是这样,那不是效率低下吗?如果没有,为什么?
答案 0 :(得分:2)
如果代码天真地被编译,则可能会有一些开销。原因是内部函数是在全新的词法环境中定义的,该词法环境在每次进入函数时都会实例化。在抽象语义中,每次调用函数时,都必须捕获新的词法闭包并将其连接到该环境框架中的正确位置。
因此,归结为编译器可以优化多少。例如,它可以注意到,这两个函数均未实际引用周围的词汇环境。 (这些函数中的x
和y
引用是它们自己的参数,而不是周围的mean-square
的参数)。这意味着它们都无需更改语义即可移至最高层:
(define (__anon1 x) (* x x))
(define (__anon2 x y) (/ (+ x y) 2))
(define (mean-square x y)
(define square __anon1)
(define average __anon2)
(average (square x) (square y)))
并且从现在开始square
和average
是有效的简单别名(对于由编译器生成的全局实体的别名,编译器知道该别名不会被其控制范围之外的任何事物操纵),它们表示的值可以通过以下方式传播:
(define (mean-square x y)
(__anon2 (__anon1 x) (__anon1 y)))
答案 1 :(得分:1)
这不是问题。编译mean-square
过程时,还将嵌套所有嵌套过程。不必每次调用mean-square
过程时都重新编译它们。
答案 2 :(得分:1)
我认为其他答案可能已经使您相信,您给出的案例确实不需要任何开销:可以将本地定义编译掉。但是,值得思考的是系统如何处理无法完成 的情况。
考虑这样的定义:
(define (make-searcher thing)
(define (search in)
(cond [(null? in)
#f]
[(eqv? (first in) thing)
in]
[else (search (rest in))]))
search)
嗯,本地search
过程绝对不能在这里编译,因为它是从make-searcher
返回的。而且甚至比这更糟:(make-searcher 1)
和(make-searcher 2)
需要返回不同的过程,因为((make-searcher 1) '(1 2 3))
是(1 2 3)
而((make-searcher 2) '(1 2 3))
是(2 3)
。
所以这听起来完全没有希望:本地search
过程不仅必须是一个过程(不能被编译掉),而且每次都必须重新制作。
但是实际上情况并没有那么糟。词法作用域意味着系统可以准确知道search
可以看到哪些绑定(在这种情况下,thing
的绑定及其参数)。因此,例如,您可以做的是编译一些代码,这些代码在向量中查找这些绑定的值。然后,从make-search
返回的内容将search
的已编译代码与绑定向量打包在一起。编译后的代码始终相同,每次只需要创建和初始化向量。
答案 3 :(得分:0)
想象一下这段代码:
(let ((a expr))
(do-something-with a))
与以下相同:
((lambda (a)
(do-something-with a))
expr)
在解释器中,它可能每次在调用lambda之前创建一次lambda
语言可能会将其变成(do-something-with expr)
。该报告除了保证尾部递归外,不希望涉及非功能性要求。在所有严肃的实现中,lambda都是便宜的。
由于您提到球拍: 文件test_com.rkt
#lang racket
(define (mean-square x y)
(define (square x) (* x x))
(define (average x y) (/ (+ x y) 2))
(average (square x) (square y)))
(display (mean-square 2 4))
端子命令:
raco make test_com.rkt
raco decompile compiled/test_com_rkt.zo
结果输出:
(module test_com ....
(require (lib "racket/main.rkt"))
(provide)
(define-values
(mean-square)
(#%closed
mean-square49
(lambda (arg0-50 arg1-51)
'#(mean-square #<path:/home/westerp/compiled/test_com.rkt> 2 0 14 136 #f)
'(flags: preserves-marks single-result)
(/ (+ (* arg0-50 arg0-50) (* arg1-51 arg1-51)) '2))))
(#%apply-values print-values (display '10)) ; the only code that matters!
(void)
(module (test_com configure-runtime) ....
(require '#%kernel (lib "racket/runtime-config.rkt"))
(provide)
(print-as-expression '#t)
(void)))
虽然mean-square
内联了本地过程,但是因为我给它提供了文字值,所以它将永远不会调用它,所以它所做的只是(display '10)
然后退出。
这当然是如果您执行make
或exe
。在DrRacket中,启用调试以及更好的跟踪和错误消息的语言选项将运行得更慢。