Може хтось пояснить? Я розумію основні поняття, що стоять за ними, але часто бачу, як вони використовуються взаємозамінно, і я плутаюся.
А тепер, коли ми тут, як вони відрізняються від звичайної функції?
Може хтось пояснить? Я розумію основні поняття, що стоять за ними, але часто бачу, як вони використовуються взаємозамінно, і я плутаюся.
А тепер, коли ми тут, як вони відрізняються від звичайної функції?
Відповіді:
Лямбда просто анонімна функція - функція , певна без імені. У деяких мовах, таких як Схема, вони еквівалентні названим функціям. Фактично, визначення функції переписується як прив'язка лямбда до змінної внутрішньо. В інших мовах, як-от 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 - ті роки , перш ніж перші електронні обчислювальні машини існували навіть, це джерело я говорю.
Calbulus Lambda - найпростіша мова програмування у світі. Єдине, що ви можете зробити в ньому: ►
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), у яких немає об'єктів для стану стану.
Цікавим є те, що впровадження лямбдасів і замикань в 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 називається вільною змінною. Повинно бути зовнішнє середовище, яке містить z. Інакше значення виразу не може бути визначене лише прив'язкою x і y. Щоб це було зрозуміло, ви можете написати щось наступне на схемі:
((lambda (z) (lambda (x y) (+ x y z))) 1)
Таким чином, z буде прив’язаний до 1 у зовнішній таблиці. Ми все ще отримуємо функцію, яка приймає два параметри, але реальний сенс її також залежить від зовнішнього середовища. Іншими словами, зовнішнє середовище закривається на вільних змінних. За допомогою набору! Ми можемо зробити функцію стаціонарною, тобто це не функція в сенсі математики. Те, що він повертає, залежить не тільки від введення даних, але й z.
Це те, що ви вже дуже добре знаєте, метод об’єктів майже завжди покладається на стан об’єктів. Ось чому деякі кажуть, що "закриття - це об'єкти бідної людини". Але ми також можемо вважати об'єкти закриттями для бідних людей, оскільки нам дуже подобаються функції першого класу.
Я використовую схему, щоб проілюструвати ідеї завдяки цій схемі є однією з найдавніших мов, яка має справжні закриття. Усі матеріали тут набагато краще представлені у розділі 3 SICP.
Підводячи підсумок, лямбда і закриття - це дійсно різні поняття. Лямбда - це функція. Закриття - це пара лямбда та відповідне середовище, яке закриває лямбда.
Концепція така ж, як описана вище, але якщо ви з фонового режиму PHP, це додатково пояснить за допомогою PHP-коду.
$input = array(1, 2, 3, 4, 5);
$output = array_filter($input, function ($v) { return $v > 2; });
функція ($ 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 (через Lambdas та закриття - яка різниця? ):
"Закриття - це лямбда-вираз, поєднаний із середовищем, яке пов'язує кожну з його вільних змінних до значення. У 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)
кудись зателефонувати, щоб скористатися цим. ось лише один приклад. Це лямбда, перш ніж для цього був синтаксис. Для цього лямбда - скорочення.
Закриття:
лямбда стає закриттям, коли вона може отримати доступ до змінних за межами цієї області. я здогадуюсь, ви можете сказати його магію, вона магічно може обернути довкілля, в якому вона була створена, і використовувати змінні поза її сферою (зовнішній обсяг. щоб бути зрозумілим, закриття означає, що лямбда може отримати доступ до своєї зовнішньої сфери.
у Котліні лямбда завжди може отримати доступ до її закриття (змінні, що знаходяться в її зовнішній області)
Це залежить від того, використовує функція зовнішня змінна чи ні для виконання операції.
Зовнішні змінні - змінні, визначені поза межами функції.
Вираження лямбда без стану, оскільки це залежить від параметрів, внутрішніх змінних чи констант для виконання операцій.
Function<Integer,Integer> lambda = t -> {
int n = 2
return t * n
}
Закриття утримує стан, оскільки він використовує зовнішні змінні (тобто змінні, визначені поза межами тіла функції), а також параметри та константи для виконання операцій.
int n = 2
Function<Integer,Integer> closure = t -> {
return t * n
}
Коли Java створює закриття, вона зберігає змінну n з функцією, щоб її можна було посилати при передачі іншим функціям або будь-де використовувати її.