简体   繁体   中英

SICP 3.6 - Rand procedure and local state variables

I'm having difficulties with exercise 3.6 in SICP. They give the following code for a pseudo-random number generator:

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

To which I added, for testing purposes:

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

Repeated applications produces

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

as hoped, although I can't see why this works. Anyway, exercise 3.6 asks us to modify rand so that it takes a single argument, instructing it to either 'generate or 'reset .

First things first, I tried to set up a rand with conditions that would generate. However, I stumbled at this first hurdle.

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

gives me

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

as does moving the let expression into the conidition, like this:

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

So why does the first function work, and the new one doesn't? Is it to do with the use of conditions? Or with the addition of an parameter?

UPDATE (19/03/20)

Reading the next section on the environment model of computation (§3.2) gave me the theory necessary to figure out what was going on. I ended up with

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

The key point for understanding why the first version works (and why the other doesn't) is in the first three lines:

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

As you can see, the name rand is assigned to a lambda - but before doing that a variable x is created in a scope outside of the lambda , meaning: that no matter how many times we call rand , the value in x will "remember" its previous value, which we set in the previous call: (set! x (rand-update x)) .

So, you must respect the placement of that let and that lambda , otherwise the procedure you create won't have any "memory" between invocations. Besides, I don't think the exercise is asking you to create your own random procedure, it'd be enough to create a wrapper around the built-in procedures that accepts the required messages:

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

(define rand (make-rand))

For example:

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

If you decide to implement your own version of random , try to keep the procedures separate (as I did above), if you put everything in a single place things will get confusing pretty soon.

x is not contained in our 'random' procedure. x is containded in the procedure that makes our 'random' procedure. The shape of the solution is:

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

(define my-rand (make-rand))

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

So make-rand returns a procedure <proc> which:

  1. given the message 'generate returns the next random number.
  2. given the message 'reset returns yet another procedure that takes a new value and assigns it to x.

Using define (named procedures) make-rand could be:

(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

The 'reset message returns a procedure, eg, (my-rand 'reset) returns set-x! so ((my-rand 'reset) 42) is equivalent to (set-x! 42) .

We could also implement make-rand using lambdas (anonymous procedures):

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

In either case, as Oscar explains, x maintains it's value because it is outside the scope of <proc> / my-rand . This is described in §3.2 and then implemented in §4.1.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM