В чем разница между "закрытием" и "лямбдой"?

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

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

Ответы

Ответ 1

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

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

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

Это вызовет ошибку, потому что func не закрывает окружающая среда в anotherfunc - h равна undefined. 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.

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

Ответ 2

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

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

  • ПРИМЕНЕНИЕ: Применение одного выражения к другому, обозначается как fx.
    (Думайте об этом как о вызове функции, где f - функция, а x - ее единственный параметр)
  • АННОТАЦИЯ: связывает символ, встречающийся в выражении, чтобы отметить, что этот символ - просто "слот", пустое поле, ожидающее заполнения значением, как бы "переменная". Это делается путем добавления греческой буквы λ (лямбда), затем символического имени (например, 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  )


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

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

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

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

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

  • ЗАКРЫТЫЕ выражения: каждый символ, встречающийся в этих выражениях, ограничен какой-то лямбда-абстракцией. Другими словами, они автономны; они не требуют какого-либо окружающего контекста для оценки. Их также называют комбинаторами.
  • Выражения 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]  }

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

Другими словами, он закрывает открытое лямбда-выражение. Именно отсюда и произошло закрытие имени, и именно поэтому ответы многих людей в этой теме не совсем верны: P


Так почему они ошибаются? Почему многие из них говорят, что замыкания - это некоторые структуры данных в памяти или некоторые особенности языков, которые они используют, или почему они путают замыкания с лямбдами? :П

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

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

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

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

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

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

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

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

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

Ответ 3

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

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)}); 

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

@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;
           };
 }

Ответ 4

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

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

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

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

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

Ответ 5

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

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

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

Ответ 6

С точки зрения языков программирования они полностью две разные вещи.

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

Lambda обеспечивает способ абстрагирования процесса вычислений. например, для вычисления суммы двух чисел, процесс, который принимает два параметра 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!, мы можем сделать функцию stateful, т.е. Это не функция в смысле математики. То, что оно возвращает, зависит не только от ввода, но и от z.

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

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

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

Ответ 7

Концепция такая же, как описано выше, но если вы находитесь на фоне 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();

Хорошо объяснено в этой статье.

Ответ 8

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

Ответ в контексте Java (через Lambdas и закрытие - в чем разница?:

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

Ответ 9

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

Ответ 10

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

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

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

Закрытие:

лямбда становится замыканием, когда она может получить доступ к переменным вне этой области. Я думаю, вы можете сказать, что это волшебство, оно волшебным образом может обернуться вокруг среды, в которой оно было создано, и использовать переменные за пределами своей области видимости (внешняя область. Поэтому, чтобы быть ясным, закрытие означает, что лямбда может получить доступ к своей ВНЕШНЕЙ ОБЛАСТИ).

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

Ответ 11

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

Ответ 12

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

Внешние переменные - переменные, определенные вне области действия функции.

  • Лямбда-выражения не сохраняют состояния, поскольку они зависят от параметров, внутренних переменных или констант для выполнения операций.

    Function<Integer,Integer> lambda = t -> {
        int n = 2
        return t * n 
    }
    
  • Замыкания содержат состояние, потому что для выполнения операций используются внешние переменные (т.е. Переменные, определенные вне области действия тела функции), а также параметры и константы.

    int n = 2
    
    Function<Integer,Integer> closure = t -> {
        return t * n 
    }
    

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