2012-02-14 7 views
5

Sto trasferendo QuickCheck in Rust e ho scritto tutto tranne for_all. Non sono sicuro di quale dovrebbe essere la firma del tipo, so solo che in generale, for_all accetterà una proprietà lambda e una raccolta di lambdas generatori. for_all valuterà i generatori per creare un caso di test casuale per fornire la proprietà come input. for_all dovrebbe stampare +++ OK, passed 100 tests. se la proprietà restituisce true. In caso contrario, dovrebbe stampare *** Failed! e stampare i valori dei casi di test incriminati.Come si emula Lisp (applica) o (curry) in Rust?

risposta

0

Puoi descrivere esattamente ciò che desideri? Penso che quello che stai chiedendo è qualcosa di simile:

+0

Non proprio. Ho difficoltà a spiegarlo. http://www.yellosoft.us/quickcheck lo fa meglio. Se riesci a riprodurre Lisp's '(apply)' in Rust, posso fare il resto. – mcandre

2

Se tutto ciò che serve è un modo per definire apply, provare le estensioni di sintassi macro-by-example di Rust:

fn main() { 
    #macro[[#apply[f, [x, ...]], f(x, ...)]]; 

    fn add(a: int, b: int) -> int { a + b } 

    assert (#apply[add, [1, 15]] == 16); 
} 

È possibile che questo il codice è da the Rust test suite.

Purtroppo la documentazione delle estensioni di sintassi è un po 'scarsa al momento. Lo Rust reference manual è probabilmente la soluzione migliore, anche se l'esempio che fornisce (di apply, nientemeno!) È obsoleto, quindi non sono sicuro di quanta parte delle sue informazioni possa essere considerata attendibile.

Aggiornamento:

Tutto quello che resta è capire come avvolgere Aggiungi ... affermarsi in una funzione con la firma giusto tipo che accetta generatori arbitrari.

io non sono ancora sicuro esattamente come si sta mettendo insieme tutto questo, ma ecco una funzione che accetta qualsiasi funzione che produce un int:

use std; 
import std::rand; 

fn assert_even(num_gen: fn() -> int) -> (bool, int) { 
    let num = num_gen(); 
    ret (num % 2 == 0, num); 
} 

fn main() { 
    let rng = rand::mk_rng(); 

    let gen_even = {|| (rng.next() as int) * 2}; 

    log(error, assert_even(gen_even)); 
} 

Tuttavia, lavorando con i numeri a Rust è una specie di dolore in questo momento, e se si vuole generalizzare assert_even a qualsiasi tipo numerico si dovrà definire interfacce/implementazioni e quindi dichiarare assert_even con un tipo generico limitata:

use std; 
import std::rand; 

iface is_even { fn is_even() -> bool; } 

impl of is_even for int { fn is_even() -> bool { self % 2 == 0 } } 

impl of is_even for u32 { fn is_even() -> bool { self % 2u == 0u } } 

fn assert_even<T: is_even>(num_gen: fn() -> T) -> (bool, T) { 
    let num = num_gen(); 
    ret (num.is_even(), num); 
} 

fn main() { 
    let rng = rand::mk_rng(); 

    let gen_even_int = {|| (rng.next() as int) * 2}; 
    let gen_even_u32 = {|| rng.next() * 2u}; 

    log(error, assert_even(gen_even_int)); 
    log(error, assert_even(gen_even_u32)); 
} 

Nota a margine: se sei interessato ai test, dovresti dare un'occhiata alle strutture tipografiche di Rust. Vedere the Rust manual here per un esempio di ciò che fa il typestate e il modo in cui è in grado di far rispettare la correttezza del programma. Per come la capisco, è fondamentalmente una versione più potente di Eiffel design by contract.

Aggiornamento 2:

for_all accetta una singola proprietà (ad esempio is_even o divisible_by) e un insieme di funzioni del generatore. I generatori sono lambda che restituiscono valori casuali da passare come input alla proprietà, ad es. [gen_int] per is_even o [gen_int, gen_int] per divisible_by. for_all chiamerà la proprietà utilizzando i valori generati come test case, stampando +++ OK, superato 100 test se la proprietà restituisce true per 100 casi di test casuali o * non riuscita! {test_case} se uno dei test fallisce.

Questo file sorgente completo dovrebbe dimostrare pienamente il comportamento che stai cercando, si spera (la definizione di for_all si trova nei pressi fondo):

use std; 
import std::rand; 
import std::io::println; 

iface is_even { fn is_even() -> bool; } 

impl of is_even for int { fn is_even() -> bool { self % 2 == 0 } } 

fn main() { 
    let rng = rand::mk_rng(); 

            // Cast to int here because u32 is lame 
    let gen_even = {|| (rng.next() as int) * 2}; 
    let gen_float = {|| rng.next_float()}; 

    // Accepts generators that produce types that implement the is_even iface 
    fn assert_even<T: is_even>(num_gen: fn() -> T) -> bool { 
     let num = num_gen(); 
     let prop_holds = num.is_even(); 
     if !prop_holds { 
      println(#fmt("Failure: %? is not even", num)); 
     } 
     ret prop_holds; 
    } 

    fn assert_divisible(num_gen1: fn() -> float, 
         num_gen2: fn() -> float) -> bool { 
     let dividend = num_gen1(), 
      divisor = num_gen2(); 
     let prop_holds = dividend/divisor == 0f; 
     if !prop_holds { 
      println(#fmt("Failure: %? are not divisible", (dividend, divisor))); 
     } 
     ret prop_holds; 
    } 

             // Begin anonymous closure here 
    #macro[[#for_all[prop, [gen, ...]], {|| 
     let passed_tests = 0; 
     let prop_holds = true; 
     // Nice iterators and break/continue are still being implemented, 
     // so this loop is a bit crude. 
     while passed_tests < 100 && prop_holds { 
      prop_holds = prop(gen, ...); 
      if prop_holds { passed_tests += 1; } 
     } 
     println(#fmt("Tests passed: %d", passed_tests)); 
     ret 0; // Necessary to infer type of #for_all, might be a compiler bug 
    }()]]; // Close anonymous closure and self-execute, then close #macro 

    #for_all[assert_even, [gen_even]]; 
    #for_all[assert_divisible, [gen_float, gen_float]]; 
} 

Ancora una cosa: il meccanismo di estensione della sintassi è ancora abbastanza rozzo, quindi non è possibile importare macro da casse diverse. Fino ad allora, la definizione di #for_all dovrà apparire nel file in cui è stata richiamata.

+0

che è enormemente utile! Tutto ciò che rimane è capire come racchiudere 'add ... assert' in una funzione con la firma del tipo giusto che accetta generatori arbitrari. Invece di '1' e' 15', funzioni che generano numeri casuali (o qualsiasi tipo). – mcandre

+0

@mcandre Puoi dare un esempio di un'invocazione 'for_all', insieme alle firme delle funzioni che prevedi di passare? –

+0

Visita https://wiki.call-cc.org/eggref/4/cluckcheck e trova o scorri fino a "per tutti". – mcandre

6

In (corrente) Ruggine, tutte le funzioni accettano un numero fisso di parametri, quindi non esiste un equivalente di apply di Lisp nel caso generale. Ma le macro possono fornirti l'astrazione che desideri. Nel nuovo, lucido, e il sistema di macro attualmente completamente privi di documenti, è possibile scrivere:

macro_rules! for_all { 
    { $tester:expr, $($generator:expr),* } 
    => { $tester($($generator()),*) } 
} 

(. Notate che stiamo sperando di iniziare a utilizzare () invece di {} nelle macro molto presto)

Poi, for_all!{ |a:int,b:int| { a+b }, || 4, || 7 } produce 11.

Nel vecchio sistema macro, l'equivalente è:

#macro[[#for_all[tester, generator, ...], tester((generator()), ...)]] 

Buona fortuna con il vostro progetto!

+1

E le macro non possono ancora essere importate correttamente, ma c'è un trucco sporco che puoi fare fino a quando non lo implementiamo: usa 'include! {"/Percorso/a/file.rs "}', e metti le definizioni delle macro in quel file, circondato da un blocco arricciato. Non dire a nessuno che ti ho detto di usare 'include!', Però. (: –

+0

Grazie per il tuo fantastico codice! L'ultima cosa di cui ho bisogno è un modo per 1) memorizzare temporaneamente i valori generati in una sorta di collezione 2) applicare la funzione tester ad ogni caso di test nella raccolta 3), in caso di test case fallisce, stampa "FAIL!"e stampa i valori in quel test case 4) passi del ciclo 1-3 in modo che 100 test case vengano eseguiti contro il tester, fermandosi in caso di fallimento di un test. Se nessuno dei 100 test fallisce, stampa" Ok ". Obi Wan Kanobi, sei la mia unica speranza. – mcandre