В чем разница между «закрытием» и «лямбдой»? - PullRequest
747 голосов
/ 21 октября 2008

Может кто-нибудь объяснить? Я понимаю основные концепции, стоящие за ними, но часто вижу, что они используются взаимозаменяемо, и я запутываюсь.

А теперь, когда мы здесь, чем они отличаются от обычной функции?

Ответы [ 11 ]

653 голосов
/ 21 октября 2008

A lambda - это просто анонимная функция - функция, определенная без имени. В некоторых языках, таких как Scheme, они эквивалентны именованным функциям. Фактически, определение функции переписывается как внутренняя привязка лямбды к переменной. В других языках, таких как Python, между ними есть некоторые (довольно ненужные) различия, но в остальном они ведут себя так же.

A закрытие - это любая функция, которая закрывает среду , в которой она была определена. Это означает, что он может обращаться к переменным, отсутствующим в его списке параметров. Примеры:

def func(): return h
def anotherfunc(h):
   return func()

Это приведет к ошибке, поскольку func не не закрывает , среда в anotherfunc - h не определена. func закрывается только в глобальной среде. Это будет работать:

def anotherfunc(h):
    def func(): return h
    return func()

Потому что здесь func определяется в anotherfunc и в Python 2.3 и выше (или некотором подобном числе), когда они почти получили корректные замыкания (мутация по-прежнему не работает), это означает, что он закрывается над окружением anotherfunc и может обращаться к переменным внутри него. В Python 3.1+ мутация также работает при использовании ключевого слова nonlocal .

Еще один важный момент - func будет продолжать закрывать среду anotherfunc, даже если она больше не оценивается в anotherfunc. Этот код также будет работать:

def anotherfunc(h):
    def func(): return h
    return func

print anotherfunc(10)()

Будет напечатано 10.

Это, как вы заметили, не имеет ничего общего с лямбда s - это два разных (хотя и связанных) понятия.

211 голосов
/ 27 апреля 2016

Существует много путаницы вокруг лямбд и закрытий, даже в ответах на этот вопрос StackOverflow здесь. Вместо того, чтобы спрашивать случайных программистов, которые узнали о замыканиях на практике с определенными языками программирования или другими невежественными программистами, отправляйтесь в путь к источнику (где все это началось). А поскольку лямбды и затворы взяты из Лямбда-исчисления , изобретенного Алонзо Черчем еще в 30-х годах, еще до того, как появились первые электронные компьютеры, это источник Я говорю о

Лямбда-исчисление - самый простой язык программирования в мире. Единственное, что в нем можно сделать: ►

  • APPLICATION: Применение одного выражения к другому, обозначается f x.
    (Думайте об этом как о вызове функции , где f - функция, а x - ее единственный параметр )
  • ABSTRACTION: привязывает символ, встречающийся в выражении, для обозначения того, что этот символ является просто «слотом», пустым полем, ожидающим заполнения значением, как бы «переменной». Это делается путем добавления греческой буквы λ (лямбда), затем символического имени (например, x), затем точки . перед выражением. Затем он преобразует выражение в функцию , ожидающую один параметр .
    Например: λx.x+2 принимает выражение x+2 и сообщает, что символ x в этом выражение - это связанная переменная - его можно заменить значением, указанным в качестве параметра.
    Обратите внимание, что функция, определенная таким образом, является анонимной - у нее нет имени, поэтому вы еще не можете обратиться к ней, но вы можете немедленно вызвать ее (помните приложение? ), предоставив ему ожидаемый параметр, например: (λx.x+2) 7. Затем выражение (в данном случае буквальное значение) 7 подставляется как x в подвыражении x+2 примененной лямбды, так что вы получаете 7+2, который затем уменьшается до 9 по общим правилам арифметики.

Итак, мы решили одну из загадок:
лямбда - это анонимная функция из приведенного выше примера λx.x+2. <Ч /> В разных языках программирования синтаксис функциональной абстракции (лямбда) может различаться. Например, в JavaScript это выглядит так:

function(x) { return x+2; }

и вы можете сразу применить его к какому-либо параметру:

(function(x) { return x+2; })(7)

или вы можете сохранить эту анонимную функцию (лямбду) в некоторой переменной:

var f = function(x) { return x+2; }

, который фактически дает ему имя f, что позволяет вам обращаться к нему и вызывать его несколько раз позже, например ::

alert(  f(7) + f(10)  );   // should print 21 in the message box

Но вам не нужно было называть это. Вы можете позвонить сразу:

alert(  function(x) { return x+2; } (7)  );  // should print 9 in the message box

В LISP лямбды сделаны так:

(lambda (x) (+ x 2))

и вы можете вызвать такую ​​лямбду, сразу применив ее к параметру:

(  (lambda (x) (+ x 2))  7  )

<ч /> Хорошо, теперь пришло время разгадать другую загадку: что такое замыкание . Для этого давайте поговорим о символах ( переменных ) в лямбда-выражениях.

Как я уже сказал, лямбда-абстракция связывает символ в своем подвыражении, так что он становится заменяемым параметром . Такой символ называется bound . Но что, если в выражении есть другие символы? Например: λx.x/y+2. В этом выражении символ x связан лямбда-абстракцией λx., предшествующей ему. Но другой символ, y, не связан - он свободен . Мы не знаем, что это такое и откуда оно берется, поэтому мы не знаем, что это означает и какое значение оно представляет, и поэтому мы не можем оценить это выражение до выяснить, что означает y.

Фактически, то же самое относится и к двум другим символам, 2 и +. Просто мы настолько знакомы с этими двумя символами, что обычно забываем, что компьютер их не знает, и нам нужно сказать, что они означают, определив их где-то, например. в библиотеке или на самом языке.

YoВы можете думать о свободных символах, как они определены где-то еще, вне выражения, в его «окружающем контексте», который называется его окружением . Окружение может быть большим выражением, частью которого является это выражение (как сказал Квай-Гон Джинн: «всегда есть большая рыба»;)), или в какой-то библиотеке, или в самом языке (как примитив ) ).

Это позволяет разделить лямбда-выражения на две категории:

  • ЗАКРЫТЫЕ выражения: каждый символ, встречающийся в этих выражениях, связан некоторой лямбда-абстракцией. Другими словами, они автономны ; они не требуют какого-либо окружающего контекста для оценки. Их также называют комбинаторами .
  • ОТКРЫТЫЕ выражения: некоторые символы в этих выражениях не связаны - то есть некоторые символы, встречающиеся в них, свободны и требуют некоторой внешней информации, и поэтому они не могут оцениваться до тех пор, пока вы не предоставите определения этих символов.

Вы можете ЗАКРЫТЬ лямбда-выражение open , предоставив среду , которая определяет все эти свободные символы, связывая их с некоторыми значениями (которые могут быть числами, строками, анонимными функциями, или лямбды, что угодно ...).

И вот идет закрытие часть:
замыкание лямбда-выражения - это конкретный набор символов, определенных во внешнем контексте (среде), которые дают значения свободных символов в этом выражении, делая они несвободны больше. Он превращает открытое лямбда-выражение, которое все еще содержит некоторые «неопределенные» свободные символы, в закрытое , которое больше не имеет свободных символов.

Например, если у вас есть следующее лямбда-выражение: λx.x/y+2, символ x связан, а символ y свободен, поэтому выражение open и не может быть оценено, если вы не скажете, что y означает (и то же самое с + и 2, которые также бесплатны). Но предположим, что у вас также есть окружение , подобное этому:

{  y: 3,
+: [built-in addition],
2: [built-in number],
q: 42,
w: 5  }

Эта среда предоставляет определения для всех "неопределенных" (свободных) символов из нашего лямбда-выражения (y, +, 2) и нескольких дополнительных символов (q, w). Символы, которые нам нужно определить, являются этим подмножеством среды:

{  y: 3,
+: [built-in addition],
2: [built-in number]  }

и это как раз замыкание нашего лямбда-выражения:>

Другими словами, закрывает открытое лямбда-выражение. Отсюда и название closure , и именно поэтому ответы многих людей в этой теме не совсем верны: P <Ч /> Так почему они ошибаются? Почему многие из них говорят, что замыкания - это некоторые структуры данных в памяти или некоторые особенности языков, которые они используют, или почему они путают замыкания с лямбдами? : P

Ну, виноваты корпоративные маркетоиды Sun / Oracle, Microsoft, Google и т. Д., Потому что именно так они называли эти конструкции на своих языках (Java, C #, Go и т. Д.). Они часто называют «замыканиями», которые должны быть просто лямбдами. Или они называют «замыкания» конкретным методом, который они использовали для реализации лексической области видимости, то есть тот факт, что функция может получить доступ к переменным, которые были определены во внешней области во время ее определения. Они часто говорят, что функция «заключает» эти переменные, то есть записывает их в некоторую структуру данных, чтобы спасти их от уничтожения после завершения выполнения внешней функции. Но это всего лишь выдуманная post factum «фольклорная этимология» и маркетинг, который только делает вещи более запутанными, потому что каждый поставщик языков использует свою собственную терминологию.

И это еще хуже из-за того факта, что в их словах всегда есть доля правды, которая не позволяет вам легко отклонить это как ложное: P Позвольте мне объяснить:

Если вы хотите реализовать язык, который использует лямбды в качестве первоклассных граждан, вам нужно разрешить им использовать символы, определенные в их окружающем контексте (то есть использовать свободные переменные в ваших лямбдах). И эти символы должны быть там, даже когда окружающая функция возвращается. Проблема в том, что эти символы привязаны к некоторому локальному хранилищу функции (обычно в стеке вызовов), которого больше не будет, когда функция вернется. Следовательно, чтобы лямбда работала так, как вы ожидаете, вам нужно каким-то образом «захватить» все эти свободные переменные из внешнего контекста и сохранить их на потом, даже когда внешний контекст исчезнет. То есть вам нужно найти замыкание вашей лямбды (все эти внешние переменные, которые она использует) и сохранить ее где-то еще (либо путем создания копии, либо путем подготовки пространства для них заранее, где-то еще, чем на стек). Фактический метод, который вы используете для достижения этой цели, является «подробностью реализации» вашего языка. Здесь важно замыкание , которое представляет собой набор свободных переменных из окружения вашей лямбды, которые нужно где-то сохранить.

Людям не потребовалось слишком много времени, чтобы начать называть фактическую структуру данных, которую они используют в реализациях своего языка, для реализации замыкания как самого замыкания. Структура обычно выглядит примерно так:

Closure {
   [pointer to the lambda function's machine code],
   [pointer to the lambda function's environment]
}

и эти структуры данных передаются в качестве параметров другим функциям, возвращаются из функций и сохраняются в переменных для представления лямбда-выражений и предоставления им доступа к окружающей их среде, а также к машинному коду для выполнения в этом контексте. Но это просто (один из многих) способ реализовать закрытие , а не само закрытие .

Как я объяснил выше, закрытие лямбда-выражения - это подмножество определений в его среде, которые дают значения свободным переменным, содержащимся в этом лямбда-выражении, фактически закрывая выражение (поворачивая open лямбда-выражение, которое еще не может быть оценено, в закрытое лямбда-выражение, которое затем может быть оценено, поскольку все символы, содержащиеся в нем, теперь определены).

Все остальное - это просто "культ груза" и "магия voo-doo" программистов и поставщиков языков, не подозревающих о реальных корнях этих понятий.

Надеюсь, это ответит на ваши вопросы. Но если у вас есть дополнительные вопросы, не стесняйтесь задавать их в комментариях, и я постараюсь объяснить их лучше.

172 голосов
/ 21 октября 2008

Когда большинство людей думает о функциях , они думают о именованных функциях :

function foo() { return "This string is returned from the 'foo' function"; }

Они называются по имени, конечно:

foo(); //returns the string above

С лямбда-выражениями вы можете иметь анонимные функции :

 @foo = lambda() {return "This is returned from a function without a name";}

В приведенном выше примере вы можете вызвать лямбду через переменную, которой она была назначена:

foo();

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

function filter(list, predicate) 
 { @filteredList = [];
   for-each (@x in list) if (predicate(x)) filteredList.add(x);
   return filteredList;
 }

//filter for even numbers
filter([0,1,2,3,4,5,6], lambda(x) {return (x mod 2 == 0)}); 

A замыкание может быть именованной или анонимной функцией, но известно как таковое, когда оно «закрывает» переменные в области, где определена функция, т. Е. Замыкание будет по-прежнему ссылаться на среду с любыми внешними переменными, которые используются в самом замыкании. Вот названное закрытие:

@x = 0;

function incrementX() { x = x + 1;}

incrementX(); // x now equals 1

Это не так много, но что, если все это было в другой функции, и вы передали incrementX внешней функции?

function foo()
 { @x = 0;

   function incrementX() 
    { x = x + 1;
      return x;
    }

   return incrementX;
 }

@y = foo(); // y = closure of incrementX over foo.x
y(); //returns 1 (y.x == 0 + 1)
y(); //returns 2 (y.x == 1 + 1)

Так вы получаете объекты с состоянием в функциональном программировании. Поскольку именование «incrementX» не требуется, вы можете использовать лямбду в этом случае:

function foo()
 { @x = 0;

   return lambda() 
           { x = x + 1;
             return x;
           };
 }
52 голосов
/ 21 октября 2008

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

Лямбда - это, по сути, функция, которая определяется внутри строки, а не стандартный метод объявления функций. Лямбды часто можно передавать как объекты.

Замыкание - это функция, которая заключает в себе окружающее ее состояние путем ссылки на поля, внешние по отношению к ее телу. Закрытое состояние остается через вызовы замыкания.

В объектно-ориентированном языке замыкания обычно предоставляются через объекты. Однако некоторые языки OO (например, C #) реализуют специальные функциональные возможности, которые ближе к определению замыканий, предоставляемых чисто функциональными языками (такими как lisp), которые не имеют объектов для включения состояния.

Что интересно, так это введение в C # Lambdas и Closures, которое приближает функциональное программирование к массовому использованию.

14 голосов
/ 19 марта 2014

Это так просто: лямбда - это языковая конструкция, то есть просто синтаксис для анонимных функций; замыкание - это метод его реализации - или любые первоклассные функции, в этом отношении, именованные или анонимные.

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

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

12 голосов
/ 19 марта 2014

С точки зрения языков программирования, это совершенно разные вещи.

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

Лямбда предоставляет способ абстрагировать процесс вычислений. например, чтобы вычислить сумму двух чисел, процесс, который принимает два параметра x, y и возвращает x + y, может быть абстрагирован. В схеме вы можете написать это как

(lambda (x y) (+ x y))

Вы можете переименовать параметры, но задание, которое оно выполняет, не изменяется. Почти во всех языках программирования вы можете дать лямбда-выражению имя, которое называется функцией. Но нет большой разницы, они могут быть концептуально рассмотрены как просто синтаксический сахар.

Хорошо, теперь представьте, как это можно реализовать. Всякий раз, когда мы применяем лямбда-выражение к некоторым выражениям, например,

((lambda (x y) (+ x y)) 2 3)

Мы можем просто заменить параметры выражением, которое нужно оценить. Эта модель уже очень мощная. Но эта модель не позволяет нам изменять значения символов, например, Мы не можем имитировать изменение статуса. Таким образом, нам нужна более сложная модель. Короче говоря, всякий раз, когда мы хотим вычислить значение лямбда-выражения, мы помещаем пару символов и соответствующее значение в среду (или таблицу). Затем остаток (+ x y) оценивается путем поиска соответствующих символов в таблице. Теперь, если мы предоставим некоторые примитивы для непосредственной работы с окружением, мы можем смоделировать изменения статуса!

С этим фоном, проверьте эту функцию:

(lambda (x y) (+ x y z))

Мы знаем, что когда мы вычисляем лямбда-выражение, x y будет связано в новой таблице. Но как и где мы можем посмотреть вверх? На самом деле z называется свободной переменной. Там должен быть внешний среда, которая содержит z. В противном случае значение выражения не может быть определено только связыванием x и y. Чтобы прояснить это, вы можете написать что-то на схеме следующим образом:

((lambda (z) (lambda (x y) (+ x y z))) 1)

Так что z будет связан с 1 во внешней таблице. Мы все еще получаем функцию, которая принимает два параметра, но реальное значение этого также зависит от внешней среды. Другими словами, внешняя среда замыкается на свободные переменные. С помощью set !, мы можем сделать функцию состоящей из состояний, то есть это не функция в смысле математики. То, что он возвращает, зависит не только от ввода, но и от z.

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

Я использую схему, чтобы проиллюстрировать идеи, потому что эта схема - один из самых ранних языков, который имеет реальные замыкания. Все материалы здесь намного лучше представлены в главе 3 SICP.

Подводя итог, лямбда и замыкание - это действительно разные понятия. Лямбда - это функция. Закрытие - это пара лямбда и соответствующая среда, которая закрывает лямбду.

7 голосов
/ 09 ноября 2014

Этот вопрос старый и получил много ответов. Теперь с Java 8 и Official Lambda, которые являются неофициальными проектами закрытия, возникает вопрос.

Ответ в контексте Java (через Лямбды и замыкания - в чем разница? ):

"Замыкание - это лямбда-выражение, соединенное со средой, которая связывает каждую из своих свободных переменных со значением. В Java лямбда-выражения будут реализованы с помощью замыканий, поэтому два термина стали взаимозаменяемыми в сообщества. "

7 голосов
/ 31 июля 2014

Концепция такая же, как описано выше, но если вы из PHP, это более подробно объясняется с использованием PHP-кода.

$input = array(1, 2, 3, 4, 5);
$output = array_filter($input, function ($v) { return $v > 2; });

function ($ v) {return $ v> 2; } - это определение лямбда-функции. Мы даже можем сохранить его в переменной, чтобы его можно было многократно использовать:

$max = function ($v) { return $v > 2; };

$input = array(1, 2, 3, 4, 5);
$output = array_filter($input, $max);

А что если вы хотите изменить максимально допустимое число в фильтруемом массиве? Вы должны написать другую лямбда-функцию или создать замыкание (PHP 5.3):

$max_comp = function ($max) {
  return function ($v) use ($max) { return $v > $max; };
};

$input = array(1, 2, 3, 4, 5);
$output = array_filter($input, $max_comp(2));

Замыкание - это функция, которая оценивается в ее собственной среде, которая имеет одну или несколько связанных переменных, к которым можно обращаться при вызове функции. Они происходят из мира функционального программирования, где существует множество концепций. Замыкания похожи на лямбда-функции, но умнее в том смысле, что они способны взаимодействовать с переменными из внешней среды, в которой определено замыкание.

Вот более простой пример закрытия PHP:

$string = "Hello World!";
$closure = function() use ($string) { echo $string; };

$closure();

Приятно объяснено в этой статье.

5 голосов
/ 16 мая 2015

Проще говоря, замыкание - это хитрость в области видимости, лямбда - анонимная функция. Мы можем реализовать замыкание с помощью лямбды более элегантно, и лямбда часто используется в качестве параметра, передаваемого в более высокую функцию

1 голос
/ 15 апреля 2019

Лямбда-выражение - это просто анонимная функция. например, в простой Java вы можете написать это так:

Function<Person, Job> mapPersonToJob = new Function<Person, Job>() {
    public Job apply(Person person) {
        Job job = new Job(person.getPersonId(), person.getJobDescription());
        return job;
    }
};

, где класс Function только что встроен в код Java. Теперь вы можете позвонить mapPersonToJob.apply(person) куда-нибудь, чтобы использовать его. это только один пример. Вот лямбда, прежде чем был синтаксис для этого. Лямбда - короткий путь для этого.

Закрытие:

Лямбда становится закрытием, когда она может получить доступ к переменным вне этой области. я думаю, вы можете сказать, что это волшебство, оно волшебным образом может обернуться вокруг среды, в которой оно было создано, и использовать переменные за пределами своей области видимости (внешняя область. поэтому, чтобы быть понятным, закрытие означает, что лямбда может получить доступ к своей ВНЕШНЕЙ СФЕРЕ.

в Kotlin лямбда всегда может получить доступ к своему замыканию (переменные, находящиеся в его внешней области видимости)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...