Понимание объектов с локальной схемой состояний

я учусь для моей финальной схемы, и объекты с локальным состоянием всегда были сложной темой.

Вот вопрос из моего выпускного экзамена, по которому мне нужна помощь.

(define (make-incrementer n)
  (let ((old 0)
        (new 0))
    (lambda ()
      (cond ((< new n) 
             (set! old new)
             (set! new (+ new 1))
             old)
            (else 
             (set! old 0)
             (set! new 1)
             old)))))

(define a (make-incrementer 3))
(define b (make-incrementer 3))
(define c a)

; 1) (a)
; 2) (a)

почему, когда a вызывается во второй раз, он возвращает 1? Я смотрю на код, и n, который мы ему даем, всегда равен 3. Так разве он не всегда подходит для случая else?


person MikanPotatos    schedule 14.05.2013    source источник
comment
Иногда понимание облегчается компоновкой кода, потому что это делает явно очевидным область применения разных имен.   -  person GoZoner    schedule 14.05.2013
comment
@GoZoner Действительно, слава Богу за C-M-\ в emacs   -  person Daniel Gratzer    schedule 14.05.2013
comment
И «M-x untabify» при публикации в SO   -  person GoZoner    schedule 14.05.2013
comment
К сожалению, я запускаю это на Windows, поэтому у меня нет emacs.   -  person MikanPotatos    schedule 14.05.2013
comment
@ user2036503: GNU Emacs доступен для Windows, последний раз, когда я смотрел.   -  person Rainer Joswig    schedule 14.05.2013


Ответы (2)


Добро пожаловать в удивительный мир замыканий! Это хрестоматийный пример того, как работают замыкания в Scheme.

Таким образом, make-counter возвращает функцию, которая имеет 3 переменные, которые она захватывает из окружающей ее среды: n, old, new. В этом случае стартовая среда выглядит примерно так

_name_|_value_
 n    | 3
 old  | 0
 new  | 1

При каждом вызове он увеличивает old и new и оборачивает их, если они больше n. Поскольку он использует set!, это увеличение изменяет переменные в среде лямбды, но, поскольку эти переменные захватываются из окружающей среды, они также изменяются для всех будущих вызовов.

Вот почему вы получаете разные результаты даже при одинаковых входных данных.

Если это похоже на колдовство, вы можете думать об этом как об объектах в более распространенных языках:

Например, Питон:

class Foo():
    def __init__(self, n, m):
        self.n = n
        self.m = m
    def count(self):
        self.n += 1
        if self.n == self.m:
           self.n = 1
        return self.n-1

f = Foo(0, 2)

f.count() # 1
f.count() # 0

Это та же основная идея, за исключением того, что здесь мы более подробно указываем, откуда берется среда, self. В Scheme мы имитируем это с помощью лямбды, захватывающей окружающие переменные.

Чтобы узнать больше, ознакомьтесь с SICP.

person Daniel Gratzer    schedule 14.05.2013
comment
главное, чего я не понимаю, разве старый и новый не должны быть больше 3, чтобы увеличивающийся набор работал? - person MikanPotatos; 14.05.2013
comment
@ user2036503 да, но они увеличиваются, (set! new (+ 1 new)) - person Daniel Gratzer; 14.05.2013
comment
я, вероятно, пропустил что-то ОЧЕНЬ ОЧЕВИДНОЕ. простите за это. Но для того, чтобы приращение работало, new не должно быть больше n. Но n равно 3, а начальный новый равен 1, поэтому он никогда не увеличивается. - person MikanPotatos; 14.05.2013
comment
Не беспокойтесь, это начинает иметь какой-то смысл? - person Daniel Gratzer; 14.05.2013
comment
может быть, что-то не так с тем, как я ввел (а), а затем снова (а). Но разве он не должен возвращать 0, а затем 0. Или я ошибаюсь? - person MikanPotatos; 14.05.2013
comment
Вы ошибаетесь :) Когда он мутирует переменные в области видимости выше лямбды, он мутирует для всех будущих вызовов лямбды. Это означает, что при втором вызове new будет 2, а не 1. Он запомнит изменение - person Daniel Gratzer; 14.05.2013
comment
Вот это да. Мне жаль . Знак на самом деле является символом ‹. Не › символ. Теперь я полностью понимаю. Все это время я думал, что это новое › n. но его новый ‹ п. - person MikanPotatos; 14.05.2013
comment
Ха-ха, тогда у нас все хорошо? Если это так, нажмите зеленую галочку рядом с моим ответом, чтобы отметить вопрос как решенный. - person Daniel Gratzer; 14.05.2013
comment
Да. извините, что потратил ваше время, я бы полностью понял это, когда увидел, но я смешал ‹ с ›. - person MikanPotatos; 14.05.2013
comment
Не беспокойтесь об этом, рад помочь - person Daniel Gratzer; 14.05.2013
comment
@ user2036503 это нередкая ошибка, хотите верьте, хотите нет. вы читаете (< new n) как меньше нового, n а не меньше нового, чем n. Бывает. :) - person Will Ness; 14.05.2013

Вот несколько примеров, которые могут помочь с концепцией захвата состояния:

(define (always x) 
  (lambda rest x))
(define always-true (always #t))
(always-true #f)
-> #t

(define (add-n n) 
  (lambda (m)
    (+ n m)))
(define add-1 (add-n 1))
(add-1 10)
-> 11

(define (complement predicate)
  (lambda (x)
    (not (predicate x)))
(define not-positive? (complement positive?))
(not-positive? -1)
-> #t

Далее приведен пример, где захваченное состояние, в данном случае l, изменено. Это похоже на ваш случай, когда new и old захвачены и изменены.

(define (nexting l)
  (lambda ()
    (if (null? l)
        '()
        (let ((answer (car l)))
          (set! l (cdr l))
          answer))))
(define next-user (nexting '(Alice Bob David)))
(next-user)
-> Alice
(next-user)
-> Bob
(next-user)
-> David
(next-user)
'()
person GoZoner    schedule 14.05.2013