TL; DR Wie andere betonten: Die Lambda-Notation ist nur eine Möglichkeit, Funktionen zu definieren, ohne gezwungen zu sein, ihnen einen Namen zu geben.
Lange Version
Ich würde gerne ein bisschen auf dieses Thema eingehen, weil ich es sehr interessant finde. Haftungsausschluss: Ich habe vor langer Zeit meinen Kurs über Lambda-Kalkül belegt. Wenn jemand mit besseren Kenntnissen Ungenauigkeiten in meiner Antwort findet, kann er mir gerne helfen, diese zu verbessern.
Beginnen wir mit Ausdrücken, zB 1 + 2
und x + 2
. Literale wie 1
und 2
werden Konstanten genannt, weil sie an bestimmte feste Werte gebunden sind.
Ein Bezeichner wie x
" Variable" muss zuerst an einen Wert gebunden werden, um ihn auszuwerten. Sie können also grundsätzlich nicht bewerten x + 1
, solange Sie nicht wissen, was x
ist.
Die Lambda-Notation bietet ein Schema zum Binden bestimmter Eingabewerte an Variablen. Ein Lambda-Ausdruck kann gebildet werden, indem λx .
vor einem existierenden Ausdruck hinzugefügt wird , z λx . x + 1
. Variable x
wird gesagt, dass frei in x + 1
und gebunden inλx . x + 1
Wie hilft dies bei der Auswertung von Ausdrücken? Wenn Sie dem Lambda-Ausdruck einen Wert hinzufügen, wie folgt
(λx . x + 1) 2
Dann können Sie den gesamten Ausdruck auswerten, indem Sie alle Vorkommen der Variablen x
durch den Wert 2 ersetzen (binden) :
(λx . x + 1) 2
2 + 1
3
Die Lambda-Notation bietet also einen allgemeinen Mechanismus, um Dinge an Variablen zu binden, die in einem Ausdrucks- / Programmblock vorkommen. Je nach Kontext entstehen so in den Programmiersprachen sehr unterschiedliche Konzepte:
- In einer rein funktionalen Sprache wie Haskell stellen Lambda-Ausdrücke Funktionen im mathematischen Sinne dar: Ein Eingabewert wird in den Lambda-Körper injiziert und ein Ausgabewert wird erzeugt.
- In vielen Sprachen (z. B. JavaScript, Python, Schema) kann die Bewertung des Körpers eines Lambda-Ausdrucks Nebenwirkungen haben. In diesem Fall kann man den Begriff Prozedur verwenden , um die Differenz für reine Funktionen zu kennzeichnen.
Abgesehen von den Unterschieden geht es bei der Lambda-Notation darum, formale Parameter zu definieren und an tatsächliche Parameter zu binden.
Der nächste Schritt besteht darin, einer Funktion / Prozedur einen Namen zu geben. In mehreren Sprachen sind Funktionen Werte wie alle anderen, sodass Sie einer Funktion wie folgt einen Namen geben können:
(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
Wie Eli Barzilay betonte, binden diese Definitionen den Namen nur f
an einen Wert, der zufällig eine Funktion ist. In dieser Hinsicht sind Funktionen, Zahlen, Zeichenfolgen und Zeichen alle Werte, die auf die gleiche Weise an Namen gebunden werden können:
(define n 42) ;; Scheme
n = 42 -- Haskell
val n: Int = 42 // Scala
var n = 42 // JavaScript
n = 42 # Python
In diesen Sprachen können Sie eine Funktion auch unter Verwendung der bekannteren (aber äquivalenten) Notation an einen Namen binden:
(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
Einige Sprachen, z. B. C, unterstützen nur die letztere Notation zum Definieren von (benannten) Funktionen.
Verschlüsse
Einige abschließende Bemerkungen zu Schließungen . Betrachten Sie den Ausdruck x + y
. Dieser enthält zwei freie Variablen. Wenn Sie x
mit der Lambda-Notation binden , erhalten Sie:
\x -> x + y
Dies ist (noch) keine Funktion, da sie noch eine freie Variable enthält y
. Sie könnten auch eine Funktion daraus machen, indem Sie Folgendes binden y
:
\x -> \y -> x + y
oder
\x y -> x + y
Das ist genau das gleiche wie die +
Funktion.
Aber Sie können zum Beispiel y
auf andere Weise binden (*):
incrementBy y = \x -> x + y
Das Ergebnis der Anwendung von FunktionsinkrementBy auf eine Zahl ist ein Abschluss, dh eine Funktion / Prozedur, deren Hauptteil eine freie Variable (z. B. y
) enthält, die an einen Wert aus der Umgebung gebunden wurde, in der der Abschluss definiert wurde.
Ebenso incrementBy 5
die Funktion (Closure), die die Zahlen um 5 erhöht.
HINWEIS (*)
Ich betrüge hier ein bisschen:
incrementBy y = \x -> x + y
ist äquivalent zu
incrementBy = \y -> \x -> x + y
Der Bindemechanismus ist also derselbe. Intuitiv stelle ich mir einen Verschluss als Teil eines komplexeren Lambda-Ausdrucks vor. Wenn diese Repräsentation erstellt wird, wurden einige der Bindungen des Mutterausdrucks bereits festgelegt und der Abschluss verwendet sie später, wenn sie ausgewertet / aufgerufen wird.