2015-07-01 15 views
7

Mam niektóre kodu, który wygląda tak:Czy istnieje niepoprawny sposób łączenia wyników funkcji, które zwracają wartości opcji?

f(a).and_then(|b| { 
    g(b).and_then(|c| { 
     h(c).map(|d| { 
      do_something_with(a, b, c, d) 
     }) 
    }) 
}) 

Gdzie f, g i h powrotną Option wartości. Muszę użyć wszystkich wartości pośrednich (a, b, c i d) w obliczeniach do_something_with. Wcięcie jest bardzo głębokie. Czy jest lepszy sposób to zrobić? Idealnie byłoby to wyglądać mniej więcej tak (co oczywiście nie działa):

try { 
    let b = f(a); 
    let c = g(b); 
    let d = h(c); 
    do_something_with(a, b, c, d) 
} rescue NonexistentValueException { 
    None 
} 
+0

zrobić wszystkie funkcje zwracają ten sam typ opcji? czy zmieniają typ? –

+1

w przypadku, gdy są: oto rozwiązanie wolne od makr: http://is.gd/eItCTh Jakiekolwiek inne rozwiązania wymagają generycznych wartości lub generałów variadic lub jakiejś cechy, na przykład [FixedSizeArray] (https://doc.rust-lang.org /nightly/core/array/trait.FixedSizeArray.html) tylko dla krotek –

Odpowiedz

4

Standardowa biblioteka Rust definiuje try! makro, które rozwiązuje ten problem Result. Makro wygląda następująco:

macro_rules! try { 
    ($expr:expr) => (match $expr { 
     $crate::result::Result::Ok(val) => val, 
     $crate::result::Result::Err(err) => { 
      return $crate::result::Result::Err($crate::convert::From::from(err)) 
     } 
    }) 
} 

Co robi jest, jeśli argument jest Err, powraca z funkcji o tej wartości Err. W przeciwnym razie wartość jest równa wartości zawiniętej w Ok. Makro może być używane tylko w funkcji, która zwraca Result, ponieważ zwraca błąd, który napotkał.

Możemy wykonać podobny makra dla Option:

macro_rules! try_opt { 
    ($expr:expr) => (match $expr { 
     ::std::option::Option::Some(val) => val, 
     ::std::option::Option::None => return None 
    }) 
} 

Następnie można użyć tego makra tak:

fn do_something(a: i32) -> Option<i32> { 
    let b = try_opt!(f(a)); 
    let c = try_opt!(g(b)); 
    let d = try_opt!(h(c)); 
    do_something_with(a, b, c, d) // wrap in Some(...) if this doesn't return an Option 
} 
5

inspirowana koncepcją try! na wynik, niech owinąć nasze własne makra do wczesnego powrotu z zakresu, jeśli monada spadnie do None.

macro_rules! get(
    ($e:expr) => (match $e { Some(e) => e, None => return None }) 
); 

(skradziony z this reddit thread)

Teraz można uruchomić swój kod liniowo:

fn blah() -> Option<...> { // ... is the return type of do_something_with() 
    let a = 123; 
    let b = get!(f(a)); 
    let c = get!(g(b)); 
    let d = get!(h(c)); 
    do_something_with(a, b, c, d) 
} 

(runnable gist)