Понимание замыканий: Функция и память

Функция — это не просто действие, а действие с памятью. Замыкания позволяют функции помнить окружение, в котором она была создана. Узнайте, как функции Lispex инкапсулируют свой контекст для создания мощной логики с сохранением состояния.

Замыкания — одна из самых мощных возможностей в Лиспексе. Это механизм, который позволяет функции «запоминать» окружение, в котором она была создана. Это руководство проведет вас через то, что такое замыкание и почему оно так полезно.

Что такое замыкание?

Простыми словами, замыкание — это функция, объединенная со своим «рюкзаком». Этот рюкзак содержит все переменные, которые были в области видимости в момент создания функции.

Это означает, что функция может получить доступ к этим «запомненным» переменным, даже если она вызывается из совершенно другой части вашей программы.

Давайте рассмотрим это на самом распространенном паттерне: «фабрика функций».

Паттерн «Фабрика функций»

Представьте, что вам нужно много функций, которые делают похожие вещи. Например, одна функция, которая добавляет 5 к числу, другая, которая добавляет 10, и еще одна, которая добавляет 100. Вместо того чтобы писать каждую вручную, вы можете создать «фабрику», которая будет производить эти функции для вас.

Шаг 1: Напишите фабричную функцию

Наша фабрика, make-adder, будет принимать один аргумент, amount-to-add, и ее задача — возвращать новую функцию.

(define (make-adder amount-to-add)
  (lambda (n)
    (+ n amount-to-add)))

Шаг 2: «Магия» замыкания

Когда мы вызываем (make-adder 5), вот что происходит:

  1. Для вызова make-adder создается новое, временное окружение.
  2. Внутри этого окружения параметр amount-to-add привязывается к значению 5.
  3. Создается функция lambda. При своем создании она «осматривается» и упаковывает все переменные, которые видит, в свой «рюкзак». Она видит, что amount-to-add равен 5, и сохраняет эту информацию.
  4. make-adder завершает работу и возвращает функцию lambda вместе с ее рюкзаком, содержащим amount-to-add = 5.

Эта возвращенная функция — лямбда плюс ее рюкзак — и есть замыкание.

Шаг 3: Используйте созданные функции

Теперь мы можем создавать специализированные функции с помощью нашей фабрики:

;; Создаем функцию, которая всегда будет добавлять 5.
(define add-five (make-adder 5))

;; Создаем функцию, которая всегда будет добавлять 10.
(define add-ten (make-adder 10))

;; Давайте их используем!
(add-five 100)
;; Результат: 105
;; Функция `add-five` заглядывает в свой «рюкзак» и вспоминает, что
;; `amount-to-add` был равен 5.

(add-ten 100)
;; Результат: 110
;; У функции `add-ten` есть свой собственный, отдельный рюкзак, где
;; `amount-to-add` был равен 10.

Почему это полезно?

Этот паттерн невероятно мощный. Он позволяет вам создавать на лету настраиваемые, предварительно сконфигурированные функции. Вы можете использовать его для:

  • Создания настраиваемых обработчиков событий.
  • Настройки функций с начальным состоянием.
  • Продвинутых паттернов, таких как каррирование и частичное применение.

Заключение

Понимание замыканий — ключ к освоению функционального программирования в Лиспексе. Просто помните: это функция + ее окружение при создании (рюкзак). С этой концепцией вы сможете писать более элегантный, модульный и повторно используемый код.

Для более формального определения вы всегда можете вернуться на страницу концепции Функции и замыкания.