2014-10-22 7 views
6

Sto cercando di creare una soluzione a Graham´s accumulator factory challenge che fondamentalmente richiede una funzione per restituire una chiusura che si chiude su una variabile numerica mutabile il cui valore iniziale viene ricevuto attraverso un parametro. Ogni chiamata a questa chiusura incrementa questa variabile acquisita di un valore che è un parametro della chiusura e restituisce il valore accumulato.Ripristino di una chiusura con ambiente mutabile

Dopo aver letto il closures RFC e alcune domande sulla restituzione di chiusure non archiviate (in particolare this). Potrei finalmente trovare una soluzione che sia stata compilata, ma il risultato non è quello che mi aspetterei.

#![feature(unboxed_closures, unboxed_closure_sugar)] 

fn accumulator_factory(n: f64) -> Box<|&mut: f64| -> f64> { 
    let mut acc = n; 
    box |&mut: i: f64| { 
     acc += i; 
     acc 
    } 
} 

fn main() { 
    let mut acc_cl = accumulator_factory(5f64); 
    println!("{}", acc_cl.call_mut((3f64,))); 
    println!("{}", acc_cl.call_mut((3f64,))); 
} 

AFAIK questa chiusura cattura acc dal valore, la struct generato che agisce come l'ambiente è mutevole e acc_cl dovrebbero mantenere la stessa istanza ambiente tra le chiamate.

Ma il risultato stampato è 6 in entrambi i casi, quindi sembra che il valore modificato non sia persistente. E ciò che è più confuso è come viene calcolato questo risultato. Ad ogni esecuzione della chiusura il valore iniziale di acc è 3 anche se n è 5 quando chiamato.

Se modificare il generatore a questo:

fn accumulator_factory(n: f64) -> Box<|&mut: f64| -> f64> { 
    println!("n {}", n); 
    let mut acc = n; 
    box |&mut: i: f64| { 
     acc += i; 
     acc 
    } 
} 

quindi l'esecuzione ritornano sempre 3 e il valore iniziale di acc è sempre 0 all'entrata chiusura.

Questa differenza di semantica sembra un bug. Ma a parte questo, perché l'ambiente si resetta tra le chiamate?

Questo è stato eseguito con rugc 0.12.0.

+1

per un altro approccio è possibile controllare questa soluzione: https://github.com/Hoverbear/rust-rosetta/blob/master/src/accumulator_factory.rs. Le nuove chiusure unboxed in ruggine sono zucchero per una struttura e un'implementazione di tratto. Questa versione scrive la lunga versione "unsugared" –

risposta

8

La modalità di acquisizione delle chiusure è stata modificata di recente. Le chiusure sono tendenziose per catturare tutto per riferimento, perché il caso d'uso più comune per chiusure è trasferirle a funzioni lungo lo stack di chiamate e catturare per riferimento rende più naturale il lavoro con l'ambiente.

A volte la cattura per riferimento è limitante. Ad esempio, non è possibile restituire tali chiusure dalle funzioni perché il loro ambiente è legato allo stack di chiamate. Per tali chiusure è necessario mettere la parola chiave move prima della chiusura:

fn accumulator_factory(n: f64) -> Box<FnMut(f64) -> f64> { 
    println!("n: {}", n); 
    let mut acc = n; 
    Box::new(move |i: f64| { 
     acc += i; 
     acc 
    }) 
} 

fn main() { 
    let mut acc = accumulator_factory(10.0); 
    println!("{}", acc(12.0)); 
    println!("{}", acc(12.0)); 
} 

Questo programma funziona come previsto:

n: 10 
22 
34 

Questi due tipi di chiusura sono coperti da this RFC.

+0

Potresti chiamare 'acc (12.0)' invece di 'acc.call_mut ((12.0,))'? La sintassi della chiamata sembra un po 'opprimente. –

+0

@MatthieuM., Ecco perché ho detto che non capisco perché le chiamate sovraccariche non funzionano. Sono anche dotati di funzionalità, ma anche con questo gate sollevato il compilatore si lamenta ancora. –

+0

@VladimirMatveev Grazie. Non ero a conoscenza di questo cambiamento di sintassi. Suppongo che le cose stiano ancora cambiando molto velocemente. Ora, quando provo a cambiare f64 in un tipo generico, ho problemi di durata. Ma scriverò un'altra domanda per quello. –