SICP 3.6 - Процедура Rand и локальные переменные состояния - PullRequest
1 голос
/ 15 марта 2020

У меня трудности с упражнением 3.6 в SICP. Они дают следующий код для генератора псевдослучайных чисел:

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

Ответы [ 2 ]

2 голосов
/ 15 марта 2020

Ключевой момент для понимания того, почему первая версия работает (а почему нет) находится в первых трех строках:

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

Как видите, присвоено имя rand lambda - но перед этим создается переменная x в области действия вне lambda, что означает: что независимо от того, сколько раз мы вызываем rand, значение в x будет «помнить» свое предыдущее значение, которое мы установили в предыдущем вызове: (set! x (rand-update x)).

Таким образом, вы должны уважать расположение этого let и lambda, в противном случае созданная вами процедура не будет иметь никакой «памяти» между вызовами. Кроме того, я не думаю, что в упражнении вас попросят создать собственную процедуру 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, попробуйте сохранить процедуры отдельно (как я делал выше), если вы поместите все в одно место, вещи получат довольно скоро сбивает с толку.

1 голос
/ 16 марта 2020

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 возвращает процедуру <proc>, которая:

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

Мы могли бы также реализовать 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)))))))

В любом случае, как объясняет Оскар, x сохраняет свое значение, поскольку оно выходит за рамки <proc> / my-rand. Это описано в п. 3.2, а затем реализовано в п. 4.1.

...