TL; DR Come altri hanno sottolineato: la notazione lambda è solo un modo per definire le funzioni senza essere costretti a dare loro un nome.
Versione lunga
Vorrei approfondire un po 'questo argomento perché lo trovo molto interessante. Disclaimer: ho seguito il mio corso sul calcolo lambda molto tempo fa. Se qualcuno con una conoscenza migliore trova inesattezze nella mia risposta, sentiti libero di aiutarmi a migliorarlo.
Iniziamo con le espressioni, ad es. 1 + 2
e x + 2
. I valori letterali come 1
e 2
sono chiamati costanti perché sono associati a valori fissi specifici.
Un identificatore come x
è chiamato variable e per valutarlo devi prima associarlo a qualche valore. Quindi, in sostanza, non puoi valutare x + 1
se non sai cos'è x
.
La notazione lambda fornisce uno schema per legare valori di input specifici alle variabili. Un'espressione lambda può essere formata aggiungendo λx .
davanti a un'espressione esistente, ad es. %codice%. Si dice che la variabile λx . x + 1
sia libera in x
e associata in x + 1
In che modo questo aiuta a valutare le espressioni? Se si alimenta un valore per l'espressione lambda, in questo modo
(λx . x + 1) 2
quindi puoi valutare l'intera espressione sostituendo (vincolando) tutte le occorrenze della variabile λx . x + 1
con il valore 2:
(λx . x + 1) 2
2 + 1
3
Quindi, la notazione lambda fornisce un meccanismo generale per legare le cose alle variabili che appaiono in un blocco espressione / programma. A seconda del contesto, ciò crea visivamente diversi concetti nei linguaggi di programmazione:
- In un linguaggio puramente funzionale come Haskell, le espressioni lambda rappresentano funzioni in senso matematico: un valore di input viene iniettato nel corpo della lambda e viene prodotto un valore di output.
- In molte lingue (ad es. JavaScript, Python, Scheme) la valutazione del corpo di un'espressione lambda può avere effetti collaterali. In questo caso si può usare il termine procedura per contrassegnare la differenza rispetto alle funzioni pure.
Oltre alle differenze, la notazione lambda riguarda la definizione di parametri formali e il loro vincolo ai parametri attuali.
Il prossimo passo, è dare un nome / una funzione / procedura.
In diverse lingue, le funzioni sono valori come qualsiasi altro, quindi puoi assegnare un nome a una funzione come segue:
(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
Come sottolineato da Eli Barzilay, queste definizioni legano il nome x
a un valore, che sembra essere una funzione. Quindi sotto questo aspetto, funzioni, numeri, stringhe, caratteri sono tutti valori che possono essere associati ai nomi nello stesso modo:
(define n 42) ;; Scheme
n = 42 -- Haskell
val n: Int = 42 // Scala
var n = 42 // JavaScript
n = 42 # Python
In queste lingue puoi anche associare una funzione a un nome usando la notazione più familiare (ma equivalente):
(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
Alcune lingue, ad es. C, supporta solo la seconda notazione per la definizione delle funzioni (denominate).
Chiusure
Alcune osservazioni finali riguardanti chiusure . Considera l'espressione f
. Questo contiene due variabili libere. Se si associa x + y
usando la notazione lambda si ottiene:
\x -> x + y
Questa non è (ancora) una funzione perché contiene ancora una variabile libera x
. Puoi creare una funzione anche vincolando y
:
\x -> \y -> x + y
o
\x y -> x + y
che equivale alla funzione y
.
Ma puoi vincolare, ad esempio, +
in un altro modo (*):
incrementBy y = \x -> x + y
Il risultato dell'applicazione della funzione incrementBy a un numero è una chiusura, ovvero una funzione / procedura il cui corpo contiene una variabile libera (ad esempio y
) che è stata associata a un valore dall'ambiente in cui è stata definita la chiusura.
Quindi y
è la funzione (chiusura) che incrementa i numeri per 5.
NOTA (*)
Sto barando un po 'qui:
incrementBy y = \x -> x + y
è equivalente a
incrementBy = \y -> \x -> x + y
quindi il meccanismo di associazione è lo stesso. Intuitivamente, penso a una chiusura che rappresenti un frammento di un'espressione lambda più complessa. Quando viene creata questa rappresentazione, alcuni dei collegamenti dell'espressione madre sono già stati impostati e la chiusura li utilizza in seguito quando viene valutato / invocato.