2016-12-06 12 views
8

Mam blok kodu, w którym wiele zmiennych opcjonalnych musi być przypisanych jednocześnie. Istnieje bardzo mała szansa, że ​​któraś z wartości będzie None, więc indywidualne przekazanie każdej nieudanej sprawy nie jest szczególnie użyteczne.Jak grupować zadania "Opcji" w Rust?

Obecnie piszę kontrole tak:

if let Some(a) = foo_a() { 
    if let Some(b) = foo_b() { 
     if let Some(c) = foo_c() { 
      if let Some(d) = foo_d() { 
       // code 
      } 
     } 
    } 
} 

Byłoby wygodne, jeśli to było możliwe do zadań grupowych. Bez tego, dodanie nowego bloku wcięcia zmiennej o jeden poziom, co dla hałaśliwych dyferencjału i niepotrzebnie powoduje głębokie wcięcie:

if let Some(a) = foo_a() && 
    let Some(b) = foo_b() && 
    let Some(c) = foo_c() && 
    let Some(d) = foo_d() 
{ 
    // code 
} 

Czy istnieje sposób, aby przypisać wiele Option sw jednym if?


Niektóre szczegóły warto zauważyć:

Pierwszą funkcją, która nie powinna zwarcie i nie nazywać innych. W przeciwnym razie może to być napisane tak:

if let (Some(a), Some(b), Some(c), Some(d)) = (foo_a(), foo_b(), foo_c(), foo_d()) { 
    // Code 
} 

Głębokie wcięcie można uniknąć stosując funkcję, ale wolałbym tego nie robić, ponieważ może nie chcieć mieć ciało w różnym zakresie ...

+2

byłem dosłownie zamiar [umieścić to jako odpowiedź] (https: //play.rust-lang .org /? gist = 19b24cb31e915860916a99f41347b727 & version = stable + backtrace = 0), dopóki nie zauważyłem twojej edycji, która zawierała zwarcie. Nie sądzę, że możliwe jest zwarcie wielu wiązań 'if let'. Istnieje [jednak otwarty dokument RFC] (https://github.com/rust-lang/rfcs/issues/929). –

+0

@SimonWhitehead, dziękuję bez wyjątku, dodano go do pytania w celu wyjaśnienia - ponieważ może być przydatny w niektórych sytuacjach. – ideasman42

Odpowiedz

4

Standardowa biblioteka nie zawiera tej dokładnej funkcjonalności, ale język umożliwia utworzenie pożądanego zachowania za pomocą małego makra.

Oto co wymyśliłem:

macro_rules! all_or_nothing { 
    ($($opt:expr),*) => {{ 
     if false $(|| $opt.is_none())* { 
      None 
     } else { 
      Some(($($opt.unwrap(),)*)) 
     } 
    }}; 
} 

można karmić je wszystkie opcje i trochę krotki zawierające wartości rozpakowanego jeśli wszystkie wartości są Some lub None W przypadku, gdy którykolwiek z tych opcji są None.

Poniżej znajduje się krótki przykład, w jaki sposób z niego korzystać:

fn main() { 
    let foo = Some(0); 
    let bar = Some(1); 
    let baz = Some(2); 
    if let Some((a, b, c)) = all_or_nothing!(foo, bar, baz) { 
     println!("foo: {}; bar: {}; baz: {}", a, b, c); 
    } else { 
     panic!("Something was `None`!"); 
    } 
} 

Oto pełny test-apartament dla makra: Rust Playground

+1

Świetna odpowiedź! Zastanawiałem się nad podejściem makro, ale nie mogłem się zorientować, jak go wdrożyć (makra wciąż mnie przerażają!). –

+1

@SimonWhitehead szczerze, jestem całkiem nowy w makrach. Powinieneś był zobaczyć moją twarz, kiedy zdałem sobie sprawę, że to działa. – SplittyDev

1

Szczerze mówiąc, ktoś powinien zauważyć o Option bycia aplikacyjnych funktor:)

Kod będzie dość brzydka bez currying wsparcie w Rust, ale to działa i że nie powinno się głośny Diff:

fn foo_a() -> Option<isize> { 
    println!("foo_a() invoked"); 
    Some(1) 
} 

fn foo_b() -> Option<isize> { 
    println!("foo_b() invoked"); 
    Some(2) 
} 

fn foo_c() -> Option<isize> { 
    println!("foo_c() invoked"); 
    Some(3) 
} 

let x = Some(|v| v) 
    .and_then(|k| foo_a().map(|v| move |x| k((v, x)))) 
    .and_then(|k| foo_b().map(|v| move |x| k((v, x)))) 
    .and_then(|k| foo_c().map(|v| move |x| k((v, x)))) 
    .map(|k| k(())); 

match x { 
    Some((a, (b, (c,())))) => 
     println!("matched: a = {}, b = {}, c = {}", a, b, c), 
    None => 
     println!("nothing matched"), 
} 
+1

Działa, ale robi się coraz gorsza z większą ilością argumentów. – SplittyDev

4

Moja pierwsza skłonność polegała na zrobieniu czegoś podobnego do swizard's answer, ale na zawinięciu w cechę, która sprawi, że łańcuch stanie się czystszy. Jest to również nieco prostsze bez potrzeby wywoływania dodatkowych funkcji.

Ma wadę zwiększania zagnieżdżania krotek.

fn foo_a() -> Option<u8> { 
    println!("foo_a() invoked"); 
    Some(1) 
} 

fn foo_b() -> Option<u8> { 
    println!("foo_b() invoked"); 
    None 
} 

fn foo_c() -> Option<u8> { 
    println!("foo_c() invoked"); 
    Some(3) 
} 

trait Thing<T> { 
    fn thing<F, U>(self, f: F) -> Option<(T, U)> where F: FnOnce() -> Option<U>; 
} 

impl<T> Thing<T> for Option<T> { 
    fn thing<F, U>(self, f: F) -> Option<(T, U)> 
     where F: FnOnce() -> Option<U> 
    { 
     self.and_then(|a| f().map(|b| (a, b))) 
    } 
} 

fn main() { 
    let x = foo_a() 
     .thing(foo_b) 
     .thing(foo_c); 

    match x { 
     Some(((a, b), c)) => println!("matched: a = {}, b = {}, c = {}", a, b, c), 
     None => println!("nothing matched"), 
    } 
} 
+0

Naprawdę podoba mi się to szczerze ... i myślę, że widzę, gdzie ta technika byłaby przydatna w tym, nad czym obecnie pracuję. Dzięki! –

10

Jak @SplittyDev said można tworzyć makra, aby uzyskać funkcjonalność chcesz.Tutaj jest alternatywnym rozwiązaniem oparte makro, które również zachowuje zachowanie krótkie spięcie:

macro_rules! iflet { 
    ([$p:pat = $e:expr] $($rest:tt)*) => { 
     if let $p = $e { 
      iflet!($($rest)*); 
     } 
    }; 
    ($b:block) => { 
     $b 
    }; 
} 


fn main() { 
    iflet!([Some(a) = foo_a()] [Some(b) = foo_b()] [Some(c) = foo_c()] { 
     println!("{} {} {}", a, b, c); 
    }); 
} 

Playground

+0

Naprawdę miła, pomniejsza brodawka, wymaga "ciała" jako argumentu makro, co sprawia, że ​​preferuję odpowiedź @ SplittyDev. – ideasman42

+0

@ ideasman42 Argument makra? O ile rozumiem, ciało może być dowolnym blokiem kodu. Przyznaję, że nie jestem tak dobrze zaznajomiony z ograniczeniami dotyczącymi makr, więc jeśli masz dobry przykład czegoś, co nie zadziałałoby z tym makrem, to z pewnością byłby to dobra poprawka do odpowiedzi dla przyszłych użytkowników. –

+0

@Eric, nie ma błędu w makrze - jak mówisz, może on przyjmować dowolny blok kodu jako argument, po prostu czyta trochę niezręcznie mając 'if macro! (Args, {body});' w porównaniu do 'if macro ! (argument): nie jest to szczególnie złe, tylko moja osobista preferencja, aby tego uniknąć - biorąc pod uwagę wybór i zakładając, że alternatywa nie jest gorsza w inny sposób. – ideasman42