2014-07-02 9 views
5

Sto provando a sovraccaricare lo right shift operator (>>) in ruggine a implement a Maybe bind.Rust Shr Operator

enum Maybe<T> { 
    Nothing, 
    Just(T) 
} 

/// Maybe bind. For example: 
/// 
/// ``` 
/// Just(1) >> |x| Just(1 + x) >> |x| Just(x * 2) 
/// ``` 
impl<'a, T, U> Shr<|&T|: 'a -> Maybe<U>, Maybe<U>> for Maybe<T> { 
    fn shr(&self, f: &|&T| -> Maybe<U>) -> Maybe<U> { 
     match *self { 
      Nothing => Nothing, 
      Just(ref x) => (*f)(x) 
     } 
    } 
} 

fn main() {} 

Tuttavia, sono in esecuzione in un errore nel tentativo di invocare la chiusura:

<anon>:15:28: 15:32 error: closure invocation in a `&` reference 
<anon>:15    Just(ref x) => (*f)(x) 
            ^~~~ 
error: aborting due to previous error 
playpen: application terminated with error code 101 
Program ended. 

Perché è errato invocare una chiusura in prestito, e come posso risolvere il problema e implementare la legano ?

Ho trovato uno similar question sullo stackoverflow, ma la ruggine è cambiata abbastanza da allora in modo che non funzioni più.

+1

Sei consapevole che questo è esattamente ciò che 'Option.and_then' fa (anche se consuma' self' e così è in grado di passare l'oggetto per valore piuttosto che per riferimento, che è distintamente superiore)? –

risposta

5

Sovraccaricare l'operatore di destra non è una buona idea qui a causa delle varie limitazioni che impone a voi. Prende tutto per riferimento, mentre quello che vuoi è prendere tutto con il valore .

Non è possibile chiamare una chiusura attraverso un riferimento immutabile; devi avere un riferimento mutabile per chiamarlo, perché potrebbe mutare il suo ambiente.

La soluzione al momento della domanda a cui si faceva riferimento era usare &fn(&A) -> B, che era una chiusura immutabile; al momento non abbiamo quel tipo; |&A| -> B è parallelo a &mut fn(&A) -> B da quel momento, che semplicemente non può funzionare perché viene eseguito attraverso un riferimento immutabile. La cosa sensata, ovviamente, è prendere il valore self in base al valore e avere la funzione |A| -> B; questo è ciò che Option.and_then, che è esattamente quello che stai cercando di implementare, fa.

In breve, ciò che si sta tentando di fare è attualmente impossibile, anche se potrebbe essere possibile nuovamente in futuro. Utilizzare un metodo regolare invece di provare a sovraccaricare l'operatore.

o semplicemente usare Option, già presente:

Some(1i).and_then(|x| Some(1 + x)) 
     .and_then(|x| Some(x * 2)) 
+0

Grazie! Avevo l'impressione che sovraccaricare l'operatore fosse al momento impossibile ma volevo solo confermarlo. Anche io non avevo visto 'and_then' prima. Lo aggiungerò sicuramente al mio arsenale. – mwhittaker

+0

Vale la pena esaminare tutti i diversi metodi disponibili per "Opzione": è piuttosto una gamma! –

3

Dopo rinunciare a overloading degli operatori e dopo armeggiare con le macro di ruggine un po ', ho capito come implementare qualche bel zucchero sintattico per il concatenamento mappe e si lega Opzione . Il codice può essere trovato in this gist ed è incluso qui per comodità:

#![feature(macro_rules)] 

macro_rules! map(
    ()         => ({}); 
    ($a:expr)        => ($a); 
    ($a:expr -> $b:expr)     => ($a.map($b)); 
    ($a:expr -> $b:expr -> $($c:expr)->*) => (map!($a.map($b) -> $($c)->*)); 
) 

macro_rules! flatbind(
    ()         => ({}); 
    ($a:expr)        => ($a); 
    ($a:expr -> $b:expr)     => ($a.and_then($b)); 
    ($a:expr -> $b:expr -> $($c:expr)->*) => (flatbind!($a.and_then($b) -> $($c)->*)); 
) 

macro_rules! bind(
    () => ({}); 
    ($a:expr) => ($a); 
    ($a:expr -> |$var:ident| $body:expr) => ($a.and_then(|$var| $body)); 
    ($a:expr -> |$var:ident| $body:expr -> $(|$vars:ident| $bodies:expr)->*) => ($a.and_then(|$var| {bind!($body -> $(|$vars| $bodies)->*)})); 
) 

fn main() { 
    // Equivalent rust code: 
    // Some("12345") 
    // .map(|s| s.to_string()) 
    // .map(|s| s.len()) 
    // .map(|l| l * l) 
    let o = map!(
     Some("12345")  -> 
     |s| s.to_string() -> 
     |s| s.len()  -> 
     |l| l * l 
    ); 
    assert!(o == Some(25)); 

    // Equivalent rust code: 
    // Some("12345") 
    // .and_then(|s| Some(s.to_string())) 
    // .and_then(|s| Some(s.len())) 
    // .and_then(|l| Some(l * l)) 
    let o = flatbind!(
     Some("12345")   -> 
     |s| Some(s.to_string()) -> 
     |s| Some(s.len())  -> 
     |l| Some(l * l) 
    ); 
    assert!(o == Some(25)); 

    // Equivalent OCaml code: 
    // Some 3 >>= fun x -> 
    // Some 4 >>= fun y -> 
    // Some 5 >>= fun z -> 
    // Some(z*z - x*x - y*y) 
    // 
    // Equivalent rust code: 
    // Some(3i).and_then(|x| { 
    //  Some(4i).and_then |y| { 
    //   Some(5i).and_then |z| { 
    //    Some(z*z - x*x - y*y) 
    //   } 
    //  } 
    // }) 
    let o = bind!(
     Some(3i) -> |x| 
     Some(4i) -> |y| 
     Some(5i) -> |z| { 
      assert!(x == 3i); 
      assert!(y == 4i); 
      assert!(z == 5i); 
      Some(z*z - x*x - y*y) 
     } 
    ); 
    assert!(o == Some(0)); 
} 
4

Questo è possibile al giorno d'oggi. Shr prende per valore, e ci sono chiusure disimballati:

use std::ops::Shr; 
use Maybe::{Nothing,Just}; 

#[derive(Debug)] 
enum Maybe<T> { 
    Nothing, 
    Just(T) 
} 

impl<T, U, F> Shr<F> for Maybe<T> 
    where F: FnOnce(T) -> Maybe<U> 
{ 
    type Output = Maybe<U>; 

    fn shr(self, f: F) -> Maybe<U> { 
     match self { 
      Nothing => Nothing, 
      Just(x) => f(x) 
     } 
    } 
} 

fn main() { 
    let a = Just(1u8); 
    let b = a >> |v: u8| Just(v + 1); 
    println!("{:?}", b) 
}