程序什么时候"intertwine definition and use"?

When does a program "intertwine definition and use"?

Footnote #28 of SICP 表示如下:

Embedded definitions must come first in a procedure body. The management is not responsible for the consequences of running programs that intertwine definition and use.

这到底是什么意思?我明白了:

但是,这种理解似乎与 的答案矛盾过程主体 依赖于也在该主体开头的定义 '。这让我很困惑:

  1. 这种解释显然与我上面所说的相矛盾,但似乎有强有力的证据——编译器规则——在其背后。
  2. SICP 似乎乐于将定义与其他使用它们的定义放在一起。看看 sqrt 过程 just above the footnote!
  3. 乍一看,在我看来,链接问题的作者真正的错误是将 num-prod 视为他们对 num 定义中的一个值,而不是一个过程。但是,作者显然已经成功了,所以我可能错了。

所以到底发生了什么?误会在哪里?

这很微妙,正如脚注和您引用的问题所暗示的那样,微妙之处可能因特定语言的实现而异。

这些问题将在本书后面(第 3 章和第 4 章)进行更详细的介绍,并且通常,本书避免使用内部定义,以便在详细检查之前可以避免这些问题。

上面脚注代码之间的关键区别:

(define (sqrt x)
  (define (good-enough? guess)
    (< (abs (- (square guess) x)) 0.001))
  (define (improve guess)
    (average guess (/ x guess)))
  (define (sqrt-iter guess)
    (if (good-enough? guess)
        guess
        (sqrt-iter (improve guess))))
  (sqrt-iter 1.0))

和另一个问题中的代码:

(define (pi-approx n)
  (define (square x) (* x x))

  (define (num-prod ind) (* (* 2 ind) (* 2 (+ ind 1)))) ; calculates the product in the numerator for a certain term
  (define (denom-prod ind) (square (+ (* ind 2 ) 1))) ;Denominator product at index ind

  (define num (product num-prod 1 inc n))
  (define denom (product denom-prod 1 inc n))

是前者的所有定义都是过程定义,而numdenom定义。在调用该过程之前,不会 评估过程的主体。但是在分配值时会评估值定义。

具有值定义:

(define sum (add 2 2))

(add 2 2) 将在评估定义时进行评估,如果这样 add 必须已经定义。但是有一个过程定义:

(define (sum n m) (add n m))

一个过程对象将被分配给 sum 但过程主体尚未计算,因此 add 不需要在定义 sum 时定义,但必须由时间 sum 被调用:

(sum 2 2)

正如我所说,有很多微妙之处和很多变化,所以我不确定以下是否 always 对于 every[=74] 是否正确=] 方案的变体,但在 'SICP scheme' 内你可以说..

有效(define 的评估顺序不重要):

;procedure body
(define (sum m n) (add m n))
(define (add m n) (+ m n))
(sum 2 2)

同样有效:

;procedure body
(define (sum) (add 2 2))
(define (add m n) (+ m n))
(sum)

通常无效(defines 的求值顺序很重要):

;procedure body
(define sum (add 2 2))
(define (add m n) (+ m n))

以下内容是否有效取决于实现:

;procedure body
(define (add m n) (+ m n))
(define sum (add 2 2))

最后是一个 交织定义和使用 的示例,这是否有效还取决于实现。 IIRC,如果 scanning out 已经实现,这将适用于本书第 4 章中描述的方案。

;procedure body
(sum)
(define (sum) (add 2 2))
(define (add m n) (+ m n))

它复杂而微妙,但重点是:

  1. 值定义的计算方式不同于过程定义,
  2. 块内的行为可能与块外不同,
  3. 方案实施之间存在差异,
  4. 这本书的设计让你在第 3 章之前不必担心这个,
  5. 本书将在第 4 章对此进行详细介绍。

你发现了scheme的难点之一。和口齿不清。由于这种 chicken and egg 问题,没有一个口齿不清,但出现了很多口齿不清。

如果 R5RS 中的 letrec 逻辑和 R6RS 中的 letrec* 逻辑的代码中不存在绑定形式,则语义为 未定义 .也就是说,一切都取决于方案实施者的意愿。

请参阅论文修复 Letrec:Scheme 的递归绑定构造的忠实而高效的实现

此外,您可以阅读 mailing list from 1986 的讨论,当时方案的不同实施者之间没有达成普遍共识。

此外,麻省理工学院开发了 2 个版本的方案——学生版和研究人员开发版,它们在 define 表格的顺序方面表现不同。

在给定过程的定义/代码中,

  • “内部定义” 是以 define.
  • 开头的形式
  • "a procedure body"是以define.
  • 开头的表格之后的所有其他表格
  • “嵌入定义必须在过程主体中首先出现” 意味着所有内部 define 形式必须首先出现,然后是所有其他内部形式。一旦出现非define内表,以后就不能再出现内define表了。
  • "禁止交织使用" 表示在定义之前不得使用任何名称。想象一下,所有内部 define 形式都聚集到一个等价的 letrec 中,并遵循 它的 规则。

也就是说,我们有,

(define (proc args ...)
   ;; internal, or "embedded", definitions
   (define a1 ...init1...)
   (define a2 ...init2...)
   ......
   (define an ...initn...)
   ;; procedure body
   exp1 exp2 .... )

Any a<sub>i</sub> 可以用在任何 init<sub>j</sub> 表达式,但只能在 lambda 表达式中。(*) 否则它将引用 a<sub>i[ 的值=109=]a<sub>j</sub> 正在被定义,这是被禁止的,因为任何 a<sub>i</sub> 名称被认为尚未定义,而任何 init<sub>j</sub> 表达式正在被定义评价。


(*) 请记住 (define (foo x) ...x...)(define foo (lambda (x) ...x...)) 相同。这就是为什么你提到的书中那个 sqrt 过程中的定义是好的——它们都是 lambda 表达式,并且在 lambda 表达式中使用的任何名称实际上只会在 lambda 时引用该名称的值表达式的值——一个 lambda 函数——将被调用,而不是在计算该 lambda 表达式时,生成作为它的值的 lambda 函数。


这本书一开始有点含糊语言的精确语义,但是in Scheme上面的代码等同于

(define proc 
   (lambda (args ...)
      ;; internal, or "embedded", definitions
      (letrec ( (a1 ...init1...)
                (a2 ...init2...)
                ......
                (an ...initn...) )
        ;; procedure body
        exp1 exp2 .... 
        )))

可以看出解释,比如here, here or here.


例如

                                   ;; or, equivalently,
(define (my-proc x)                (define my-proc
                                     (lambda (x)
   (define (foo) a)                     (letrec ( (foo (lambda () a))
   (define a x)                                   (a x) )
   ;; my-proc's body                       ;; letrec's body
   (foo))                                  (foo))))

首先计算 lambda 表达式,(lambda () a),并将名称 foo 绑定到结果,一个函数;该函数引用a的值(foo)将被调用时,所以有一个引用就可以了到该 lambda 表达式中的 a——因为当计算该 lambda 表达式时,不需要立即 a 的值,只需要对其 future 值的引用,以 a 的名字存在;即 a all 中的名称初始化后 letrec 的值,以及 letrec 输入。或者换句话说,当所有内部 define 完成并进入程序 my-proc 的主体时。

所以我们看到 foo 已定义,但在初始化期间未使用; a 已定义但在初始化期间未使用;因此一切都很好。但是如果我们有例如

(define (my-proc x)
   (define (foo) x)    ; `foo` is defined as a function
   (define a (foo))    ; `foo` is used by being called as a function
   a)

然后这里 foo 被定义为 在内部初始化期间使用,或“嵌入”,define;这在 Scheme 中是被禁止的。这就是这本书警告的内容:内部定义只允许定义东西,但它的使用应该延迟到以后,当我们完成内部 defines 并进入完整过程的主体时。