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 migliore conoscenza trova delle imprecisioni nella mia risposta, sentiti libero di aiutarmi a migliorarlo.
Cominciamo con le espressioni, per esempio 1 + 2
e x + 2
. Letterali come 1
e 2
sono chiamati costanti perché associati a valori fissi specifici.
Un identificatore come x
viene chiamato variabile e per valutarlo è necessario prima associarlo a un valore. Quindi, fondamentalmente, non puoi valutare x + 1
finché non sai cosa x
sia.
La notazione lambda fornisce uno schema per l'associazione di valori di input specifici alle variabili. Un'espressione lambda può essere formato aggiungendo λx .
davanti un'espressione esistente, ad es λx . x + 1
. Variabile x
è detto di essere liberi in x + 1
e rilegato inλx . x + 1
In che modo aiuta a valutare le espressioni? Se dai un valore all'espressione lambda, in questo modo
(λx . x + 1) 2
quindi puoi valutare l'intera espressione sostituendo (vincolando) tutte le occorrenze della variabile x
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 di espressioni / programmi. A seconda del contesto, ciò crea concetti visivamente diversi 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 di lambda e viene prodotto un valore di output.
- In molti linguaggi (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.
A parte le differenze, la notazione lambda riguarda la definizione di parametri formali e il loro legame con parametri reali.
Il prossimo passo è dare un nome a una funzione / procedura. In diverse lingue, le funzioni sono valori come le altre, 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 ha sottolineato Eli Barzilay, queste definizioni associano il nome f
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 allo 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 esempio C, supportano solo quest'ultima notazione per la definizione di funzioni (denominate).
chiusure
Alcune osservazioni finali sulle chiusure . Considera l'espressione x + y
. Questo contiene due variabili libere. Se ti leghi x
usando la notazione lambda otterrai:
\x -> x + y
Questa non è (ancora) una funzione perché contiene ancora una variabile libera y
. Puoi anche farne una funzione vincolando y
:
\x -> \y -> x + y
o
\x y -> x + y
che è esattamente la stessa della +
funzione.
Ma puoi legare, diciamo, y
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 vincolata a un valore dall'ambiente in cui è stata definita la chiusura.
Così incrementBy 5
è la funzione (chiusura) che incrementa i numeri di 5.
NOTA (*)
Sto tradendo un po 'qui:
incrementBy y = \x -> x + y
è equivalente a
incrementBy = \y -> \x -> x + y
quindi il meccanismo di legame è lo stesso. Intuitivamente, penso a una chiusura come a rappresentare un pezzo di un'espressione lambda più complessa. Quando viene creata questa rappresentazione, alcuni dei vincoli dell'espressione madre sono già stati impostati e la chiusura li utilizza in seguito quando viene valutata / invocata.