Небольшой вопрос по SICP/Scheme (местный штат)

StackOverflow https://stackoverflow.com/questions/1169825

  •  19-09-2019
  •  | 
  •  

Вопрос

На самом деле я читаю книгу для развлечения, но ее можно считать домашним заданием.В любом случае, я вообще не чувствую себя комфортно с локальными переменными состояния в этом языке...Возьмем, к примеру, этот код:

(define flip
  (let ((count 0))
    (lambda ()
      (if (= 0 count)
          (begin (set! count 1) count)
          (begin (set! count 0) count)))))

почему этот код чередуется между 1 и 0?count присваивается значение 0 каждый раз, когда вызывается эта функция!Эквивалент Python будет:

class Flip:
    def __init__(self):
        pass
    def __call__(self):
        count = 0
        if count == 0:
            count = 1
            return count
        else:
            count = 0
            return count

Это каждый раз возвращает одно и то же.Я в замешательстве...

Это было полезно?

Решение

У меня есть небольшой опыт написания компиляторов для функциональных языков, поэтому, возможно, уместно краткое описание того, как эта функция хранится/представляется в памяти.Грубо говоря, каждую функцию можно представить как пару (E,F), где E — набор свободных переменных, а F — «код» самой функции.Когда вы вызываете функцию, она принимает значения из E и заменяет ими переменные в F, а затем выполняет код, используя эти значения.

Итак, что касается вашего примера, вы определили переменную «flip» как функцию, возвращаемую вашим выражением let.Эта функция — это то, что находится внутри вашей лямбды.Поскольку «count» определяется вне лямбды, это свободная переменная, поэтому она хранится в среде функции.Затем каждый раз, когда вы вызываете (переворачиваете), интерпретатор обращается к коду в лямбде, видит, что ему нужно найти значение «count» в среде, делает это, меняет его и возвращает результат.Вот почему каждый раз, когда вы его вызываете, значение, хранящееся в «count», сохраняется.

Если вы хотите, чтобы счетчик был равен нулю каждый раз, когда вы вызываете флип, поместите выражение let внутри лямбды, чтобы это была связанная переменная, а не свободная переменная.

Другие советы

Лямбда — это замыкание.Это функция, которая ссылается на свободную переменную (count), которая, не определённая локально или не являющаяся одним из параметров, привязана к ближайшему включающему лексическому окружению.

Вызываемая функция — это лямбда, а не «переворот».Flip — это просто имя, которое вы дали лямбде, возвращаемой из выражения (let...).

Что касается Python, я не знаю этого языка, но похоже, что count должен быть членом объекта Flip, а не локальной переменной. вызов.

Потому что ваша функция переворота на самом деле возвращает функцию (который определен внутри лямбды)

Каждый раз, когда вы вызываете возвращаемую функцию, она изменяет свою среду.

Если подумать об этом, позволять создает среду (и инициализирует счетчик равным 0) только один раз — когда вам возвращается лямбда-функция.

В некотором смысле лямбда создает для вас объект функции, который использует среду, последний кадр которой был инициализирован в позволять с одной переменной count.Каждый раз, когда вы вызываете свою функцию, она изменяет свою среду.Если вы позвоните подбросить во второй раз он возвращает другой объект функции с другой средой.(счетчик инициализирован равным 0). Затем вы можете переключать два функтора независимо.

Если вы хотите полностью понять, как это работает, вам следует прочитать о экологическая модель.

это больше похоже на

class Flip:
    def __init__(self):
        self.count = 0
    def __call__(self):
        if self.count == 0:
            self.count = 1
            return self.count
        else:
            self.count = 0
            return self.count

Обновление с дополнительным объяснением:Функция в Scheme представляет собой замыкание, которое «замыкается» вокруг свободной переменной. count, который определен в области вне его.То, как count определяется в let только функция в качестве тела означает, что функция — единственное, что может получить к ней доступ, что делает count по сути, это своего рода частное изменяемое состояние, прикрепленное к функции.

Именно таким способом традиционно создаются «объекты» в Scheme в SICP — чтобы иметь let определите группу переменных (переменные экземпляра, инициализированные их начальными значениями), а в теле определите группу функций, которые являются «методами», имеющими общий доступ к переменным экземпляра.Вот почему здесь естественно использовать класс Python для представления того, что происходит, с count является переменной экземпляра.

Более дословный перевод на Python 3.x будет примерно таким (обратите внимание, что это лишь приблизительный перевод, поскольку в Python нет let (объявление локальной переменной с ограниченной областью действия) и синтаксис Python lambdas нельзя использовать, поскольку они не принимают операторы):

count = 0

def flip():
    nonlocal count
    if count == 0:
        count = 1
        return count
    else:
        count = 0
        return count

# pretend count isn't in scope after this

Проблема исходного кода в том, что на него сильно влияет императивный стиль.Более идиоматическим решением будет:

(define (flip)
  (let ((flag #t))
    (lambda ()
      (set! flag (not flag))
      (if flag 1 0))))

Чтобы ответить на вопрос в вашем комментарии, ooboo, вам нужна функция, которая возвращает функцию

(define make-flipper
  (lambda ()
    (let ((count 0))
      (lambda ()
    (let ((v count))
      (set! count (- 1 count))
      v)))))

;; test it
(let ((flip-1 (make-flipper)))
  (format #t "~s~%" (flip-1))  
  (format #t "~s~%" (flip-1))
  (format #t "~s~%" (flip-1))

  (let ((flip-2 (make-flipper)))
    (format #t "~s~%" (flip-2))
    (format #t "~s~%" (flip-2))
    (format #t "~s~%" (flip-2))))

Можно банально поменять комплект!линию, чтобы она стала фишкой, а не флиппером (более полезно).

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top