Re: Rust
On 04/06/2015 12:23, Gian Uberto Lauri wrote:
[snip]
Se ci fossero (non ho ancora controllato) anche funzioni di ordine
superiore il vantaggio sarebbe ulteriormente incrementato, che queste
cose richiedono non poco lavoro in C.
Ci sono, ci sono anche se la forma con cui scrivi una funzione e quella
con cui scrivi una lambda/closure sono differenti. MOLTO differenti.
Tanto per fare un esempio, una funzione che costruisce un addizionatore
e che possiamo usare così:
fn main()
{
let add3 = make_adder(3);
let four = add3(1);
println!("Se aggiungo 3 a 1 ottengo {}!", four);
}
In un linguaggio puramente funzionale, con un runtime che alloca
automaticamente la closure sullo heap ti aspetti di poter scrivere
qualcosa come segue:
fn make_adder(n: i32) -> (Fn(i32) -> i32)
{
|x| { x + n }
}
Questo codice, per quanto sintatticamente valido, non compila. Senza
scendere troppo nei dettagli ci sono due problemi:
1) Il compilatore deve sapere la dimensione dei tipi restituiti da una
funzione e il tipo "Fn(i32) -> i32" non ha una dimensione fissa. Fn() è
un trait, un'interfaccia, la dimensione dipende dall'implementazione di
quella che alla fine è una "struct" contenente le variabili su cui
"chiudiamo" (in questo caso "n"). Il compilatore _potrebbe_ essere
sufficientemente intelligente da determinare lui implementazione esatta
e dimensioni ma... in questo caso non lo è.
2) La variabile "n" che viene passata alla funzione che crea la closure
vive sullo stack del chiamante (il valore "3" in main nel mio esempio) e
andrebbe a morire già alla seconda riga, in quanto non più referenziata.
Quindi il compilatore si rifiuta di compilare codice che potrebbe
utilizzare valori in memoria già liberata.
Per risolvere (1) ci viene in aiuto il tipo generico Box, che alloca
memoria sullo heap, e la nostra funzione diventa:
fn make_adder(n: i32) -> Box<Fn(i32) -> i32>
{
Box::new(|x| { x + n })
}
Ora make_adder restituisce quello che fondamentalmente è un puntatore,
quindi (1) è OK. Per risolvere (2) diciamo al compilatore che la closure
ha una semantica "move", ovvero che diventa proprietaria delle variabili
su cui chiude. In questo caso non è un problema, perché "3" non verrà
mai più utilizzato. La funzione, che finalmente compila, diventa:
fn make_adder(n: i32) -> Box<Fn(i32) -> i32>
{
Box::new(move |x| { x + n })
}
Fortunatamente Box è abbastanza intelligente da comportarsi in maniera
trasparente rispetto ai suoi "contenuti", quindi main non richiede
alcuna modifica.
Dati linguaggi come LISP, Haskell o anche solo F# questa cosa è
parecchio macchinosa (blargh blargh blargh, orribile!).
Del resto Rust si pone ad un livello più basso di questi linguaggi e
cerca di fare il più possibile senza aggiungere sintassi ad-hoc, ma
costruendo librerie di utilità con un compilatore che implementa poche
regole.
Inoltre, siamo solo alla 1.0: non è detto che la 1.x non avrà un
migliore supporto per le HOF.
Hope this helps,
federico
--
Federico Di Gregorio federico.digregorio@dndg.it
Di Nunzio & Di Gregorio srl http://dndg.it
Io non sono romantica. La candelina sul tavolo mi vede e si spegne.
-- sisterconfusion
Reply to:
- Follow-Ups:
- Re: Rust
- From: "Gian Uberto Lauri" <saint@eng.it>
- References:
- Rust
- From: Piviul <piviul@riminilug.it>
- Re: Rust
- From: Federico Di Gregorio <fog@dndg.it>
- Re: Rust
- From: onetmt <onetmt@gmail.com>
- Re: Rust
- From: "Gian Uberto Lauri" <saint@eng.it>
- Re: Rust
- From: onetmt <onetmt@gmail.com>
- Re: Rust
- From: "Gian Uberto Lauri" <saint@eng.it>
- Re: Rust
- From: onetmt <onetmt@gmail.com>
- Re: Rust
- From: "franchi@modula.net" <franchi@modula.net>
- Re: Rust
- From: "Gian Uberto Lauri" <saint@eng.it>