Diciannovesima lezione
Cosa sono le espressioni Lambda
Lo stile di programmazione imperativo (sequenza di comandi 'compilata' e fissa)
a cui siamo stati abituati fin qui non è l'unico possibile. Può
essere sostituito in modo equivalente con uno stile in cui per ogni operazione
viene costruita in modo dinamico una funzione apposita e 'anonima' che contiene
le operazioni da fare ed i loro argomenti. Questo
stile di programmazione funzionale è alla base del linguaggio LISP
dal lontano 1958. Ad esempio, le direttive per stampare il coseno di 3.14 in
LISP sono:
(write-line (write-to-string (cos 3.14)))
Il concetto di legare le funzioni alle variabili viene utilizzato
anche in un sistema logico-matematico formale detto
Lambda calculus.
Non è quindi un'idea nuova, ma sta avendo una esplosione di
popolarità negli anni 201x:, anche nel C++:
Lambda expressions - lambdas - are a game changer in C++ programming.
That's somewhat surprising, because they bring no expressive power to the
language. Everything a lambda can do is something you can do by hand with
a bit more typing. But lambdas are such a convenient way to create function
objects, the impact on day-to-day C++ software development is enormous.
Scott Meyers, Effective Modern C++, Chapter 6.
Cerchiamo quindi di capire come funziona e quali sono i suoi vantaggi
pratici.
Il primo valore aggiunto delle espressioni lambda in C++ è facilitare,
ed in qualche modo rendere preferibile, l'uso degli algoritmi della
libreria standard. Proviamo a confrontare con gli esempi ancora presenti
in cplusplus.com questo
La Closure
Le espressioni lambda consentono di creare a runtime delle closure,
oggetti funzione che possono anche contenere valori o reference a variabili
presenti nello scope in cui vengono creati. Per poter essere
inclusa in una closure una variabile deve essere:
- Locale e accessibile nella funzione che contiene l'espressione lambda
(anche un parametro della funzione).
- Non
static.
- Non un data member della classe a cui la funzione appartiene.
L'espressione fra parentesi quadre indica in che modo devono essere
catturate le variabili:
[=] - per valore
[&] - per reference
[] non catturare nessuna variabile
[a,&b] - a per valore e b per
reference.
[pw = std::move(pw)] - (solo C++14, ma emulabile con
std::bind in C++11 o std::bind1st in
C++98) cattura per inizializzazione.
Ad esempio:
Attenzione: se si costruisce una closure con reference a variabili
locali per una operazione che non viene eseguita immediatamente,
la closure si troverà a contenere dangling references.
Uso nella programmazione asincrona
Il solo fatto di richiedere meno sforzo alla tastiera, o di produrre codice
più leggibile perchè non costringe a cercare la definizione
di funzioni brevi lontano da dove vengono utilizzate non basta da solo
a spiegare le ragioni della loro introduzione. Neanche basta a
giustificarle il fatto che il popolarissimo linguaggio Javascript le includa
fra le sue poche caratteristiche essenziale.
Le lambda function rendono in qualche modo accessibile un modello di
programmazione dell'I/O non sincrono/sequenziale, non basato su 'event loop',
non basato su programmazione propriamente parallela (thread), ma
task-based. Le varie operazioni in attesa
vengono accodate e gestite quando sono pronte. Al loro completamento
(con o senza errore) viene invocata una opportuna funzione di call-back.
La libreria boost::asio
permette di realizzare un simile modello di I/O asincrono, e anche
in questo caso ci si può avvantaggiare delle espressioni Lambda.
Il sito di Boost offre tutorial per la creazione sia di
clienti TCP
che di server TCP
sincroni e
asincroni, ma si limita a fornire esempi C++98. Commentiamo
ora questo
, che implementa un cliente HTTP asincrono attraverso una cascata di callback.
Esercizio:
Provare ad implementare una comunicazione TCP con boost::asio.