TL; DR Як зазначали інші: позначення лямбда - це лише спосіб визначити функції, не змушуючи їх давати ім'я.
Довга версія
Я хотів би трохи детальніше розглянути цю тему, тому що я вважаю це дуже цікавим. Відмова: Я давно взяв курс на обчислення лямбда. Якщо хтось з кращими знаннями виявить у моїй відповіді неточності, не соромтесь допомогти мені покращити її.
Почнемо з виразів, наприклад, 1 + 2
і x + 2
. Літерали, такі як 1
і 2
називаються константами, оскільки вони прив'язані до певних фіксованих значень.
Такий ідентифікатор, як, наприклад, x
називається змінним, і для його оцінки потрібно спочатку прив'язати його до якогось значення. Отже, ви не можете оцінити x + 1
, доки не знаєте, що x
таке.
Позначення лямбда надає схему прив'язки певних вхідних значень до змінних. Лямбда - вираз може бути сформовано шляхом додавання λx .
в передній частині існуючого вираження, наприклад λx . x + 1
. Мінлива x
називається вільним в x + 1
і пов'язані вλx . x + 1
Як це допомагає в оцінці виразів? Якщо ви вводите значення для виразу лямбда, так
(λx . x + 1) 2
то ви можете оцінити весь вираз, замінивши (прив’язуючи) всі входи змінної x
значенням 2:
(λx . x + 1) 2
2 + 1
3
Отже, лямбда-позначення забезпечує загальний механізм прив'язки речей до змінних, які відображаються в блоці вираження / програми. Залежно від контексту, це створює надзвичайно різні поняття в мовах програмування:
- У чисто функціональній мові, як Haskell, лямбда-вирази представляють функції в математичному сенсі: вхідне значення вводиться в тіло лямбда і виробляється вихідне значення.
- У багатьох мовах (наприклад, JavaScript, Python, Scheme) оцінка тіла лямбда-виразу може мати побічні ефекти. У цьому випадку можна використовувати термін процедура для позначення різниці wrt чистих функцій.
Крім відмінностей, лямбда-позначення стосується визначення формальних параметрів та прив'язки їх до фактичних параметрів.
Наступним кроком є надання імені функції / процедури. У кількох мовах функції є значеннями, як і будь-які інші, тому ви можете надати функцію імені наступним чином:
(define f (lambda (x) (+ x 1))) ;; Scheme
f = \x -> x + 1 -- Haskell
val f: (Int => Int) = x => x + 1 // Scala
var f = function(x) { return x + 1 } // JavaScript
f = lambda x: x + 1 # Python
Як зазначав Елі Барзілай, це визначення просто пов'язує ім'я f
зі значенням, яке, можливо, є функцією. Тож у цьому відношенні функції, числа, рядки, символи - це всі значення, які можна зв'язати з іменами однаково:
(define n 42) ;; Scheme
n = 42 -- Haskell
val n: Int = 42 // Scala
var n = 42 // JavaScript
n = 42 # Python
У цих мовах ви також можете прив’язати функцію до імені, використовуючи більш звичне (але еквівалентне) позначення:
(define (f x) (+ x 1)) ;; Scheme
f x = x + 1 -- Haskell
def f(x: Int): Int = x + 1 // Scala
function f(x) { return x + 1 } // JavaScript
def f(x): return x + 1 # Python
Деякі мови, наприклад, C, підтримують лише останні позначення для визначення (названих) функцій.
Закриття
Кінцеві зауваження щодо закриття . Розглянемо вираз x + y
. Це містить дві вільні змінні. Якщо ви зв’яжете x
за допомогою лямбда-позначення, ви отримаєте:
\x -> x + y
Це ще не є функцією, оскільки вона все ще містить вільну змінну y
. Ви можете зробити з нього функцію, y
також зв’язавши:
\x -> \y -> x + y
або
\x y -> x + y
що так само, як і +
функція.
Але ви можете зв’язати, скажімо, y
іншим способом (*):
incrementBy y = \x -> x + y
Результатом застосування функції incrementBy до числа є закриття, тобто функція / процедура, тіло якої містить вільну змінну (наприклад y
), яка була прив'язана до значення із середовища, в якому було визначено закриття.
Так incrementBy 5
само функція (закриття), що збільшує числа на 5.
ПРИМІТКА (*)
Я тут трохи обманюю:
incrementBy y = \x -> x + y
еквівалентно
incrementBy = \y -> \x -> x + y
тому механізм зв’язування однаковий. Інтуїтивно я вважаю, що закриття є частиною більш складного лямбдаського виразу. Коли це представлення створено, деякі з прив'язок материнського вираження вже встановлені, і закриття використовує їх пізніше, коли його оцінюють / викликають.