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

function lambda functional-programming closures

112488 просмотра

11 ответа

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

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

Автор: sker Источник Размещён: 17.05.2019 02:34

Ответы (11)


648 плюса

Решение

Лямбда просто анонимная функция - функция , определенная без имени. В некоторых языках, таких как 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.

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

Автор: Claudiu Размещён: 21.10.2008 03:58

203 плюса

Существует много путаницы вокруг лямбд и закрытий, даже в ответах на этот вопрос 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не связан - он свободен . Мы не знаем, что это такое и откуда это происходит, поэтому мы не знаем, что это значит и какую ценность оно представляет, и поэтому мы не можем оценить это выражение, пока не выясним, что это yзначит.

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

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

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

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

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

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

For example, if you have the following lambda expression: λx.x/y+2, the symbol x is bound, while the symbol y is free, therefore the expression is open and cannot be evaluated unless you say what y means (and the same with + and 2, which are also free). But suppose that you also have an environment like this:

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

This environment supplies definitions for all the "undefined" (free) symbols from our lambda expression (y, +, 2), and several extra symbols (q, w). The symbols that we need to be defined are this subset of the environment:

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

and this is precisely the closure of our lambda expression :>

In other words, it closes an open lambda expression. This is where the name closure came from in the first place, and this is why so many people's answers in this thread are not quite correct :P


So why are they mistaken? Why do so many of them say that closures are some data structures in memory, or some features of the languages they use, or why do they confuse closures with lambdas? :P

Well, the corporate marketoids of Sun/Oracle, Microsoft, Google etc. are to blame, because that's what they called these constructs in their languages (Java, C#, Go etc.). They often call "closures" what are supposed to be just lambdas. Or they call "closures" a particular technique they used to implement lexical scoping, that is, the fact that a function can access the variables that were defined in its outer scope at the time of its definition. They often say that the function "encloses" these variables, that is, captures them into some data structure to save them from being destroyed after the outer function finishes executing. But this is just made-up post factum "folklore etymology" and marketing, which only makes things more confusing, because every language vendor uses its own terminology.

And it's even worse because of the fact that there's always a bit of truth in what they say, which does not allow you to easily dismiss it as false :P Let me explain:

If you want to implement a language that uses lambdas as first-class citizens, you need to allow them to use symbols defined in their surrounding context (that is, to use free variables in your lambdas). And these symbols must be there even when the surrounding function returns. The problem is that these symbols are bound to some local storage of the function (usually on the call stack), which won't be there anymore when the function returns. Therefore, in order for a lambda to work the way you expect, you need to somehow "capture" all these free variables from its outer context and save them for later, even when the outer context will be gone. That is, you need to find the closure of your lambda (all these external variables it uses) and store it somewhere else (either by making a copy, or by preparing space for them upfront, somewhere else than on the stack). The actual method you use to achieve this goal is an "implementation detail" of your language. What's important here is the closure, which is the set of free variables from the environment of your lambda that need to be saved somewhere.

It didn't took too long for people to start calling the actual data structure they use in their language's implementations to implement closure as the "closure" itself. The structure usually looks something like this:

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

and these data structures are being passed around as parameters to other functions, returned from functions, and stored in variables, to represent lambdas, and allowing them to access their enclosing environment as well as the machine code to run in that context. But it's just a way (one of many) to implement closure, not the closure itself.

As I explained above, the closure of a lambda expression is the subset of definitions in its environment that give values to the free variables contained in that lambda expression, effectively closing the expression (turning an open lambda expression, which cannot be evaluated yet, into a closed lambda expression, which can then be evaluated, since all the symbols contained in it are now defined).

Anything else is just a "cargo cult" and "voo-doo magic" of programmers and language vendors unaware of the real roots of these notions.

I hope that answers your questions. But if you had any follow-up questions, feel free to ask them in the comments, and I'll try to explain it better.

Автор: SasQ Размещён: 27.04.2016 01:18

171 плюса

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

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;
           };
 }
Автор: Mark Cidade Размещён: 21.10.2008 03:46

52 плюса

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

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

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

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

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

Автор: Michael Brown Размещён: 21.10.2008 03:29

14 плюса

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

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

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

Автор: Andreas Rossberg Размещён: 19.03.2014 08:31

12 плюса

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

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

Лямбда предоставляет способ, которым вы можете абстрагировать вычислительный процесс. например, чтобы вычислить сумму двух чисел, процесс, который принимает два параметра 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.

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

Автор: Wei Qiu Размещён: 19.03.2014 12:11

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

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

Автор: Developer Размещён: 31.07.2014 07:41

7 плюса

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

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

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

Автор: philippe lhardy Размещён: 09.11.2014 05:46

5 плюса

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

Автор: feilengcui008 Размещён: 16.05.2015 07:03

0 плюса

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

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

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

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

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

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

Автор: DIPANSHU GOYAL Размещён: 24.09.2018 09:34

0 плюса

Лямбда-выражение - это просто анонимная функция. в простой 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 лямбда всегда может получить доступ к своему закрытию (переменные, которые находятся в его внешней области видимости)

Автор: j2emanue Размещён: 14.04.2019 09:57
Вопросы из категории :
32x32