Ресурсы, связанные с замыканиями в ML?

#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, или книгу Аппеля «Компиляция с продолжениями» и т.д.