Может кто-нибудь объяснить? Я понимаю основные концепции, стоящие за ними, но часто вижу, что они используются взаимозаменяемо, и я запутываюсь.
И теперь, когда мы здесь, чем они отличаются от обычной функции?
Может кто-нибудь объяснить? Я понимаю основные концепции, стоящие за ними, но часто вижу, что они используются взаимозаменяемо, и я запутываюсь.
И теперь, когда мы здесь, чем они отличаются от обычной функции?
Ответы:
Лямбда просто анонимная функция - функция , определенная без имени. В некоторых языках, таких как Scheme, они эквивалентны именованным функциям. Фактически, определение функции переписывается как внутренняя привязка лямбды к переменной. В других языках, таких как Python, между ними есть некоторые (довольно ненужные) различия, но в остальном они ведут себя так же.
Замыкание любая функция , которая закрывает над на окружающую среду , в которой она была определена. Это означает, что он может обращаться к переменным, отсутствующим в его списке параметров. Примеры:
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.
Это, как вы заметили, не имеет ничего общего с лямбдами - это два разных (хотя и связанных) понятия.
Существует много путаницы вокруг лямбд и закрытий, даже в ответах на этот вопрос StackOverflow здесь. Вместо того, чтобы расспрашивать случайных программистов, которые узнали о замыканиях на практике с определенными языками программирования или другими невежественными программистами, отправляйтесь в путь к источнику (где все это началось). А так как лямбды и закрытие происходят из Lambda Исчисление изобретен Алонзо Черч еще в 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
.
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
и +
. Просто мы настолько знакомы с этими двумя символами, что обычно забываем, что компьютер их не знает, и нам нужно сказать, что они означают, определив их где-то, например, в библиотеке или в самом языке.
Вы можете думать о свободных символах как определенных где-то еще, вне выражения, в его «окружающем контексте», который называется его окружением . Окружение может быть большим выражением, частью которого является это выражение (как сказал Куай-Гон Джинн: «всегда есть большая рыба»;)), или в какой-то библиотеке, или в самом языке (как примитив ).
Это позволяет нам разделить лямбда-выражения на две категории:
Вы можете ЗАКРЫТЬ открытое лямбда-выражение, предоставив среду , которая определяет все эти свободные символы, связывая их с некоторыми значениями (которые могут быть числами, строками, анонимными функциями, т. Е. Лямбдами, что угодно…).
И вот идет закрытие часть: замыкание из лямбда - выражений это определенный набор символов , определенных во внешнем контексте (среда) , которые дают значения для свободных символов в этом выражении, что делает их несвободными больше. Он превращает открытое лямбда-выражение, которое все еще содержит «неопределенные» свободные символы, в закрытое , в котором больше нет свободных символов.
Например, если у вас есть следующее лямбда-выражение: λ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, C #, Go и т. Д.). Они часто называют «замыканиями», которые должны быть просто лямбдами. Или они называют «замыкания» конкретным методом, который они использовали для реализации лексической области видимости, то есть тот факт, что функция может получить доступ к переменным, которые были определены во внешней области во время ее определения. Они часто говорят, что функция «заключает» эти переменные, то есть захватывает их в некоторую структуру данных, чтобы сохранить их от уничтожения после завершения выполнения внешней функции. Но это только придуманная постфактум "фольклорная этимология" и маркетинг, которая только делает вещи более запутанными,
И это еще хуже из-за того факта, что в их словах всегда есть доля правды, которая не позволяет вам легко отклонить это как ложное: P Позвольте мне объяснить:
Если вы хотите реализовать язык, который использует лямбды в качестве граждан первого класса, вам нужно разрешить им использовать символы, определенные в их окружающем контексте (то есть использовать свободные переменные в ваших лямбдах). И эти символы должны быть там, даже когда окружающая функция возвращается. Проблема в том, что эти символы привязаны к некоторому локальному хранилищу функции (обычно в стеке вызовов), которого больше не будет, когда функция вернется. Следовательно, чтобы лямбда работала так, как вы ожидаете, вам нужно каким-то образом «захватить» все эти свободные переменные из внешнего контекста и сохранить их на потом, даже когда внешний контекст исчезнет. То есть нужно найти замыканиевашей лямбды (все эти внешние переменные, которые он использует) и сохраните ее где-нибудь еще (либо сделав копию, либо подготовив пространство для них заранее, где-то еще, чем в стеке). Фактический метод, который вы используете для достижения этой цели, является «подробностью реализации» вашего языка. Здесь важно замыкание , представляющее собой набор свободных переменных из окружения вашей лямбды, которые нужно где-то сохранить.
Людям не потребовалось слишком много времени, чтобы начать называть фактическую структуру данных, которую они используют в реализациях своего языка, чтобы реализовать замыкание как само замыкание. Структура обычно выглядит примерно так:
Closure {
[pointer to the lambda function's machine code],
[pointer to the lambda function's environment]
}
и эти структуры данных передаются в качестве параметров другим функциям, возвращаются из функций и сохраняются в переменных для представления лямбда-выражений и предоставления им доступа к окружающей их среде, а также к машинному коду для выполнения в этом контексте. Но это просто способ (один из многих) реализовать замыкание, а не само замыкание.
Как я объяснил выше, закрытие лямбда-выражения - это подмножество определений в его среде, которые присваивают значения свободным переменным, содержащимся в этом лямбда-выражении, эффективно закрывая выражение (превращая открытое лямбда-выражение, которое еще не может быть оценено, в закрытая лямбда - выражение, которое затем может быть оценено, так как все символы , содержащиеся в нем теперь определены).
Все остальное - это просто «культ груза» и «магия воу-ду» программистов и поставщиков языков, не подозревающих о реальных корнях этих понятий.
Я надеюсь, что это отвечает на ваши вопросы. Но если у вас есть дополнительные вопросы, не стесняйтесь задавать их в комментариях, и я постараюсь объяснить их лучше.
Когда большинство людей думают о функциях , они думают о названных функциях :
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;
};
}
Не все замыкания являются лямбдами, и не все лямбды являются замыканиями. Оба являются функциями, но не обязательно так, как мы привыкли знать.
Лямбда - это, по сути, функция, которая определяется внутри строки, а не стандартный метод объявления функций. Лямбды часто можно передавать как объекты.
Замыкание - это функция, которая заключает в себе окружающее ее состояние путем обращения к полям, внешним по отношению к его телу. Закрытое состояние остается через вызовы замыкания.
В объектно-ориентированном языке замыкания обычно предоставляются через объекты. Однако некоторые языки OO (например, C #) реализуют специальные функциональные возможности, которые ближе к определению замыканий, предоставляемых чисто функциональными языками (такими как lisp), которые не имеют объектов для включения состояния.
Интересно то, что введение Lambdas и Closures в C # приближает функциональное программирование к общепринятому использованию.
Это так просто: лямбда - это языковая конструкция, то есть просто синтаксис для анонимных функций; замыкание - это метод его реализации - или любые первоклассные функции, в этом отношении, именованные или анонимные.
Точнее говоря, замыкание - это то, как функция первого класса представляется во время выполнения в виде пары ее «кода» и окружения, «замыкающего» все нелокальные переменные, используемые в этом коде. Таким образом, эти переменные по-прежнему доступны, даже если внешние области, в которых они происходят, уже закрыты.
К сожалению, существует множество языков, которые не поддерживают функции как первоклассные значения или поддерживают их только в ограниченном виде. Поэтому люди часто используют термин «закрытие», чтобы отличить «настоящую вещь».
С точки зрения языков программирования, это совершенно разные вещи.
В основном для полного языка Тьюринга нам нужны только очень ограниченные элементы, например, абстракция, применение и редукция. Абстракция и приложение предоставляют способ построения выражения лямбда, а сокращение определяет значение выражения лямбда.
Лямбда предоставляет способ, которым вы можете абстрагировать вычислительный процесс. например, чтобы вычислить сумму двух чисел, процесс, который принимает два параметра x, y и возвращает x + y, может быть абстрагирован. В схеме вы можете написать это как
(lambda (x y) (+ x y))
Вы можете переименовать параметры, но задача, которую он выполняет, не изменяется. Почти во всех языках программирования вы можете дать лямбда-выражению имя, которое называется функцией. Но нет большой разницы, они могут концептуально рассматриваться как просто синтаксический сахар.
Хорошо, теперь представьте, как это можно реализовать. Всякий раз, когда мы применяем лямбда-выражение к некоторым выражениям, например,
((lambda (x y) (+ x y)) 2 3)
Мы можем просто заменить параметры выражением для оценки. Эта модель уже очень мощная. Но эта модель не позволяет нам изменять значения символов, например, мы не можем имитировать изменение статуса. Таким образом, нам нужна более сложная модель. Короче говоря, всякий раз, когда мы хотим вычислить значение лямбда-выражения, мы помещаем пару символов и соответствующее значение в среду (или таблицу). Затем остаток (+ xy) оценивается путем поиска соответствующих символов в таблице. Теперь, если мы предоставим некоторые примитивы для непосредственной работы с окружающей средой, мы можем смоделировать изменения статуса!
С этим фоном, проверьте эту функцию:
(lambda (x y) (+ x y z))
Мы знаем, что когда мы вычисляем лямбда-выражение, xy будет связано в новой таблице. Но как и где мы можем посмотреть вверх? На самом деле z называется свободной переменной. Должна быть внешняя среда, которая содержит z. В противном случае значение выражения не может быть определено только связыванием x и y. Чтобы прояснить это, вы можете написать что-то в схеме следующим образом:
((lambda (z) (lambda (x y) (+ x y z))) 1)
Таким образом, z будет привязан к 1 во внешней таблице. Мы все еще получаем функцию, которая принимает два параметра, но реальное значение этого также зависит от внешней среды. Другими словами, внешняя среда замыкается на свободные переменные. С помощью set !, мы можем сделать функцию состоящей из состояний, то есть это не функция в смысле математики. То, что он возвращает, зависит не только от ввода, но и от 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 и Official Lambda, которые являются неофициальными проектами закрытия, это поднимает вопрос.
Ответ в контексте Java (через лямбды и замыкания - какая разница? ):
«Закрытие - это лямбда-выражение, соединенное со средой, которая связывает каждую из своих свободных переменных со значением. В Java лямбда-выражения будут реализованы с помощью замыканий, поэтому два термина стали взаимозаменяемыми в сообществе».
Проще говоря, замыкание - это уловка в области видимости, лямбда - анонимная функция. Мы можем реализовать замыкание с помощью лямбды более элегантно, и лямбда часто используется в качестве параметра, передаваемого в более высокую функцию
Лямбда-выражение - это просто анонимная функция. в простой 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 лямбда всегда может получить доступ к своему закрытию (переменные, которые находятся в его внешней области видимости)
Это зависит от того, использует ли функция внешнюю переменную или нет для выполнения операции.
Внешние переменные - переменные, определенные вне области действия функции.
Лямбда-выражения не сохраняют состояния, поскольку они зависят от параметров, внутренних переменных или констант для выполнения операций.
Function<Integer,Integer> lambda = t -> {
int n = 2
return t * n
}
Замыкания содержат состояние, потому что для выполнения операций используются внешние переменные (т. Е. Переменные, определенные вне области действия тела функции), а также параметры и константы.
int n = 2
Function<Integer,Integer> closure = t -> {
return t * n
}
Когда Java создает замыкание, она сохраняет переменную n вместе с функцией, чтобы на нее можно было ссылаться, когда она передается другим функциям или используется где угодно.