sicp-ex-3.6



<< Previous exercise (3.5) | Index | Next exercise (3.7) >>


 (define rand 
   (let ((x random-init)) 
     (define (dispatch message) 
       (cond ((eq? message 'generate) 
               (begin (set! x (rand-update x)) 
                      x)) 
             ((eq? message 'reset) 
               (lambda (new-value) (set! x new-value))))) 
     dispatch)) 

Test:

 (define random-init 0) 
 (define (rand-update x) (+ x 1)) ; A not-very-evolved PNRG 
 (rand 'generate) 
 ; 1 
 (rand 'generate) 
 ; 2 
 ((rand 'reset) 0) 
 ; 0 
 (rand 'generate) 
 ; 1 

It's interesting to notice that the lambda returned by a call to (rand 'reset) still has the closure we created as lexical scope:

 x 
 ; Error: undefined variable 'x'. 

hi-artem

Here, inside (cond ..) construct, using "begin" is unnecessary.


Shawn

We could also define rand as a procedure instead of a variable:

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

pa3

Shawn's version will not work actually. It will always return the result of evaluating (rand-update random-input), because the state (x variable) is being recreated and reset to random-input each time rand function invoked.


tf3

Just chiming in with pa3, Shawn is really wrong. Even I made the same mistake and thankfully came here to compare. The block (let ((x random-init)) ..) is part of the function's body, so it would be called every time the function is called, which would defeat the purpose of assignment. The book didn't highlight this, or maybe we didn't read too closely, but using the variable approach would prevent the variable assignment from executing except the first time.


joew

 #lang sicp 
 ;"random" number generator 
 (define (rand-update x) 
   (let ((a 27) (b 26) (m 127)) 
     (modulo (+ (* a x) b) m))) 
  
 (define x1 (rand-update 0)) 
 (define x2 (rand-update x1)) 
 (define x3 (rand-update x2)) 
 (define x4 (rand-update x3)) 
 (define x5 (rand-update x4)) 
 x1 
 x2 
 x3 
 x4 
 x5 
 ;function that returns a closure 
 ;closures seem to be the way to initialize objects with state in scheme 
 (define (r) 
   (let ((seed 0)) 
     (define (dispatch m) 
       (cond ((eq? m 'reset) (lambda (x)(set! seed x))) 
             ((eq? m 'generate) (begin (set! seed (rand-update seed)) 
                                       seed)) 
             (else error "invalid operation"))) 
     dispatch)) 
 ;define rand in terms of r and the closure it returns 
 (define rand (r)) 
 ;test that rand works first with no args assuming 0 as seed 
 (rand 'generate) 
 (rand 'generate) 
 (rand 'generate) 
 ;test that pattern reoccurs 
 ((rand 'reset) 0) 
 (rand 'generate) 
 (rand 'generate) 
 (rand 'generate) 
 ;test the rand works using a new seed 
 ((rand 'reset) 42) 
 (rand 'generate) 
 (rand 'generate) 
 (rand 'generate) 
 ;test the pattern reoccurs 
 ((rand 'reset) 42) 
 (rand 'generate) 
 (rand 'generate) 
 (rand 'generate) 

codybartfast

Just for the laughs here are two (functionally) identical implementations, one using define to create named procedures:

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

and the other using lambda to create anonymous procedures:

(define (make-rand)
  (let ((x 0))
    (lambda (message)
      (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)))))))

Usage:

(define (make-rand)
  ...)

(define my-rand (make-rand))

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

horeszko

solution using a linear congruential generator. using the sicp language package in Racket.

#lang sicp

(define (make-rand)
  ;; returns a procedure that either generates the next pseudorandom number in a linear
  ;; congruential sequence or resets the current number in the sequence to the specified
  ;; value
  (let ((rand-num 0)) ;; our random number with initial value set to 0
    (define (set-random-init n) (set! rand-num n))
    (define m (expt 2 31)) ;; modulus used for linear congruential generator
    (define (rand-update x)
      ;; rand-update generates a random number using a linear congruential generator.
      ;; see TAOCP Vol 2 Knuth 1981 for details.
      ;; see https://en.wikipedia.org/wiki/Linear_congruential_generator#Parameters_in_common_use
      ;; for common values of parameters a, b and m
      (let ((a 1103515245) 
            (b 12345))
        (modulo (+ (* a x) b) m)))
    (define (rand-generator)
      (set! rand-num (rand-update rand-num))
      rand-num)
    (define (dispatch message)
      ;; generate next pseudorandom number in sequence
      (cond ((eq? message 'generate) (rand-generator))
            ;; reset current number in sequence
            ((eq? message 'reset) (lambda (n) (set-random-init n)))
            (else (error "Unknown request: MAKE-RAND"))))
    dispatch))

(define rand (make-rand))

;; test

(rand 'generate)
(rand 'generate)
(rand 'generate)
((rand 'reset) 12345)
(rand 'generate)
((rand 'reset) 0)
(rand 'generate)

output:

————— run ex3-6.rkt —————
12345
1406932606
654583775
1406932606
12345