【问题标题】:SICP 3.6 - Rand procedure and local state variablesSICP 3.6 - 兰德过程和局部状态变量
【发布时间】:2020-03-15 11:51:14
【问题描述】:

我在 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

首先,我尝试设置一个带有会生成条件的 rand。然而,我在第一个障碍中绊倒了。

(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))

【问题讨论】:

  • “虽然我不明白为什么会这样” -> 那么,在尝试修改它之前,您需要花时间了解为什么它会起作用!

标签: functional-programming scheme sicp


【解决方案1】:

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

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

如您所见,名称rand 被分配给lambda - 但在此之前,在lambda外部 范围内创建了一个变量x,这意味着:无论我们调用多少次randx 中的值都会“记住”它之前的值,这是我们在之前的调用中设置的:(set! x (rand-update x))

因此,您必须尊重letlambda 的位置,否则您创建的过程在调用之间不会有任何“内存”。此外,我不认为该练习要求您创建自己的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 版本,请尝试将程序分开(就像我在上面所做的那样),如果您将所有内容放在一个地方,事情很快就会变得混乱。

【讨论】:

    【解决方案2】:

    x 不包含在我们的“随机”程序中。 x 包含在制作我们的“随机”程序的程序中。解的形状是:

    (define (make-rand)
      (define x 0)
      ...
      <proc>)
    
    (define my-rand (make-rand))
    
    ((my-rand 'reset) 42)
    (my-rand 'generate)
    (my-rand 'generate)
    

    所以make-rand 返回一个过程&lt;proc&gt;

    1. 给定消息'generate 返回下一个随机数。
    2. 鉴于消息'reset 返回另一个过程,该过程接受一个新值并将其分配给x。

    使用define(命名过程)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 消息返回一个过程,例如,(my-rand 'reset) 返回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 保持其价值,因为它超出了&lt;proc&gt;/my-rand 的范围。这在 §3.2 中进行了描述,然后在 §4.1 中实现。

    【讨论】:

    • 谢谢 - 实际上,必要的理论最终在 §3.2 中涵盖。这是关于 SICP 的一些稍微令人沮丧的事情之一,有时您需要提前阅读才能进行练习!最后我做了和你类似的事情,并更新了最初的帖子。
    猜你喜欢
    • 2014-12-29
    • 1970-01-01
    • 1970-01-01
    • 2018-08-05
    • 1970-01-01
    • 2020-08-24
    • 2015-10-04
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多