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

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

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

11 ответов


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

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

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's среды и может получить доступ к переменным внутри него. В Python 3.1+ мутация также работает при использовании the nonlocal ключевое слово.

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

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

print anotherfunc(10)()

это будет печатать 10.

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


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

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

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

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

  • применение: применение одного выражения к другому, обозначается f x.
    (подумайте об этом как вызов функции, где 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, не связан – это свободный. Мы не знаем, что это такое и откуда оно взялось, поэтому мы не знаем, что это такое означает и что стоимостью он представляет, и поэтому мы не можем оценить это выражение, Пока мы не выясним, что?!--30--> средства.

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

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

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

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

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

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

например, если у вас есть следующее лямбда-выражение: λ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


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

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

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

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

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

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

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

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

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

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


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

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

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

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

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


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

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

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


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

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


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

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

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


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


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


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

внешние переменные - переменные, определенные вне функции.

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

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

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

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