#closures #ml
#Замыкания #ml
Вопрос:
Я беру класс, который использует ML, и мы рассматриваем замыкания, но я не совсем понимаю их, особенно в ML. Я делал заметки в классе, и они не имеют для меня особого смысла / предоставляют достаточно подробностей. Я попытался поискать в Интернете дополнительную информацию, но не смог ее найти.
Кто-нибудь знает какие-либо ресурсы о замыканиях в ML (или о ML / closures в целом), которые довольно хороши?
Или, если кто-нибудь может опубликовать несколько общих мыслей / объяснений о том, как реализовать замыкание в ML или как будет выглядеть замыкание в ML, что такое замыкание и т.д. Я был бы действительно признателен. Я просто пытаюсь понять концепцию / использование замыканий.
Заранее спасибо!
Ответ №1:
Я не знаю никакого ML, поэтому мой ответ будет на Scheme, который является другим функциональным языком программирования. Я предположу, что вы не знаете Scheme, и хорошо прокомментируете мой код.
** Примечание: Схема динамически типизируется, поэтому вы не увидите никаких объявлений типов (таких как int, float, char и т.д.).
(define (double x) ;this defines a new function called "double" which takes one argument called x
(* 2 x)) ;return 2 times its argument. Function application in Scheme takes the form (function arg1 arg2 ...)
Мы бы использовали эту недавно определенную функцию следующим образом:
(double 10) ;returns 20
(double 8) ;returns 16
В этой функции переменная x
является локальной переменной. x
является формальным параметром double. Всякий раз, когда мы используем x
в теле double, нет сомнений в том, какое значение мы имеем в виду. Но как насчет этого:
(define (foo x) ;define a function "foo" which takes on argument called x
(* x a)) ;return x times a
Еще раз, x
это формальный параметр. Но как насчет a
? Что мы имеем в виду, когда используем a
в теле foo? a
не определено в foo, поэтому называется свободной переменной. Чтобы увидеть, что a
означает, мы должны заглянуть за пределы foo. Например, предположим, что foo был определен в этом контексте:
(define (bar a) ;define a function "bar" which takes one argument called "a"
(define (foo x) ;define "foo" as an inner function of bar
(* x a))) ;foo returns x * a
Теперь мы знаем, что a
означает. Схема (а также ML) имеет лексическую область, что означает, что для выяснения значения переменной a
мы смотрим на текстовый контекст (надеюсь, это имеет смысл).
Итак, приведенное выше определение bar
на самом деле неверно: даже мысль foo
возвращает что-то, bar
ничего не возвращает. Это определяется foo
как внутренняя функция, но тогда после этого определения нет инструкции. Давайте исправим это:
(define (bar a) ;define a function "bar" which takes one argument called "a"
(define (foo x) ;define "foo" as an inner function of bar
(* x a)) ;foo returns x * a
foo) ;bar returns the function foo
Теперь bar
возвращает функцию foo
. Мы только что превратились bar
в функцию более высокого порядка, потому что она возвращает другую функцию.
Но есть проблема. Обычно при bar
возврате локальная переменная a
больше не нужна, а ее значение теряется. Но foo
все еще ссылается на a
! Так что a
нужно задержаться еще на некоторое время. Вот тут-то и пригодятся замыкания. Значение a
является «закрытым», поэтому оно сохраняется до тех пор, пока функция foo
существует. Таким образом, мы можем вызывать foo
даже после bar
завершения выполнения. Теперь давайте переименуем foo
и bar
, чтобы понять, почему это полезно:
(define (make-multiplier a)
(define (multiplier x)
(* x a))
multiplier)
Теперь мы можем сделать это:
(define triple (make-multiplier 3)) ;call make-multiplier with the value 3. Bind the function which is returned to the variable "triple."
(triple 5) ;call triple with the value 5. Since the 3 was "closed over", (triple 5) returns 5 * 3, which is 15.
Итак, когда функция имеет «свободную переменную», для функции создается замыкание, которое «закрывает» свободную переменную и сохраняет ее в течение всего срока службы функции. Таким образом, когда функция передается по кругу и покидает контекст, в котором она была определена, «свободная переменная» продолжает быть действительной.
Ответ №2:
замыкания — это средство в ML (или в Ocaml, Scheme, Lisp) для реализации функций. Итак, все функции являются замыканиями (то есть сочетанием кода и данных). Например (используя синтаксис Ocaml)
(* function making an incrementer, returning a function *)
let make_incr i = fun x -> x i;;
(* use it to define the successor function *)
let succ = make_incr 1;;
(* compute the successor of 4 *)
succ 4;;
Интерпретатор ocaml, конечно, успешно отвечает
val make_incr : int -> int -> int = <fun>
val succ : int -> int = <fun>
- : int = 5
вы видите, что make_incr — это функция высокого порядка: при задании некоторого целого числа она создает новую функцию. Итак, учитывая 1, что он выдает succ
в приведенном выше примере. И succ
содержит как код сложения, так и целое число 1. Таким образом, он смешивает код и данные (среду для замкнутых переменных) в замыкании.
Прочитайте больше статьи Википедии о замыкании и любой хороший учебник: SICP (автор Сассман), или C.Queinnec Lisp in Small Pieces, или любую хорошую вводную книгу по Ocaml, или книгу Аппеля «Компиляция с продолжениями» и т.д.