SICP 3.6 - Rand 过程和局部状态变量

SICP 3.6 - Rand procedure and local state variables

我在 SICP 中练习 3.6 时遇到困难。 他们为伪随机数生成器提供了以下代码:

(define rand
  (let ((x random-init))
    (lambda ()
      (set! x (rand-update x))
      x)))

为了测试目的,我添加了:

(define (rand-update x) (+ x 1))
(define random-init 4)

重复应用产生

> (rand)
5
> (rand)
6
> (rand)
7

正如所希望的那样,虽然我不明白为什么会这样。 无论如何,练习 3.6 要求我们修改 rand 以便它接受一个参数,指示它为 'generate'reset.

首先,我尝试设置一个具有生成条件的兰特。然而,我在这第一个障碍上绊倒了。

(define (rand-new instruction)
  (let ((x random-init))
    (cond ((eq? instruction 'generate)
          (lambda ()
            (set! x (rand-update x))
            x)))))

给我

> ((rand-new 'generate))
5
> ((rand-new 'generate))
5
> ((rand-new 'generate))
5

就像将 let 表达式移动到条件中一样,如下所示:

(define (rand-new instruction)
  (cond ((eq? instruction 'generate)
         (let ((x random-init))
           (lambda ()
             (set! x (rand-update x))
             x)))))

那么,为什么第一个功能有效,而新功能却无效? 跟使用条件有关系吗?或者加上一个参数?

更新(19/03/20)

阅读有关计算环境模型的下一节(§3.2),我获得了弄清楚发生了什么所必需的理论。 我结束了

(define (random-number-generator initial update)
  (define (generate)
    (begin (set! initial (update initial))
          initial))
  (define (reset new-value)
    (begin (set! initial new-value)
           initial))
  (define (dispatch message)
    (cond ((eq? message 'generate) (generate))
          ((eq? message 'reset) reset)
          (else "Procedure not found!")))
  dispatch)

(define rand (random-number-generator 5 rand-update))

理解为什么第一个版本有效(以及为什么另一个不有效)的关键点在前三行:

(define rand
  (let ((x random-init))
    (lambda ()

如您所见,名称 rand 被分配给 lambda - 但在此之前,变量 x 在范围 outside[ lambda的=36=],意思是:无论我们调用rand多少次,x中的值都会"remember"它之前的值,我们在上一个电话:(set! x (rand-update x))

因此,您必须尊重 letlambda 的位置,否则您创建的过程将没有任何 "memory" 在调用之间。此外,我不认为练习要求您创建自己的 random 过程,围绕接受所需消息的内置过程创建一个包装器就足够了:

(define (make-rand)
  (λ (msg)
    (case msg
      ('reset (λ (seed) (random-seed seed)))
      ('generate (random)))))

(define rand (make-rand))

例如:

((rand 'reset) 42)
(rand 'generate)
=> 0.07337258110323383
(rand 'generate)
=> 0.0887121382290788
((rand 'reset) 42)
(rand 'generate)
=> 0.07337258110323383
(rand 'generate)
=> 0.0887121382290788

如果您决定实现自己的 random 版本,请尝试将过程分开(就像我上面所做的那样),如果您将所有内容都放在一个地方,事情很快就会变得混乱。

x 不包含在我们的 'random' 过程中。 x包含在使我们的'random'程序的程序中。解的形状为:

(define (make-rand)
  (define x 0)
  ...
  <proc>)

(define my-rand (make-rand))

((my-rand 'reset) 42)
(my-rand 'generate)
(my-rand 'generate)

所以 make-rand returns 一个过程 <proc> 其中:

  1. 给定消息'generatereturns下一个随机数。
  2. 给定消息 'reset returns 另一个 过程接受新值并将其分配给 x。

使用定义(命名过程)make-rand 可以是:

(define (make-rand)
  (define x 0)
  (define (set-x! new-x)
    (set! x new-x))
  (define (dispatch message)
    (cond
      ((eq? message 'generate)
       (set! x (rand-update x))
       x)
      ((eq? message 'reset)
       set-x!)
      (else ((error "Unknown Message - " message)))))
  dispatch) ; 'dispatch' returned and assigned to my-rand

'reset 消息 returns 一个过程,例如 (my-rand 'reset) returns set-x! 所以 ((my-rand 'reset) 42) 等同于 (set-x! 42).

我们还可以使用 lambdas(匿名过程)实现 make-rand

(define (make-rand)
  (let ((x 0))
    (lambda (message) ; lambda returned and assigned to my-rand
      (cond
        ((eq? message 'generate)
         (set! x (rand-update x))
         x)
        ((eq? message 'reset)
         (lambda (new-x) (set! x new-x)))
        (else ((error "Unknown Message - " message)))))))

无论哪种情况,正如 Oscar 解释的那样,x 保持其价值,因为它在 <proc>/my-rand 的范围之外。这在 §3.2 中描述,然后在 §4.1 中实现。