Macro map_for::map_for [] [src]

macro_rules! map_for {
    (@process ($($move:tt)*) ($($n:ident),+) <- $e:expr; => $e0:expr) => { ... };
    (@process ($($move:tt)*) ($($n:ident),+) <- $e:expr; if $g:expr; $($tail:tt)+) => { ... };
    (@process ($($move:tt)*) ($($n:ident),+) <- $e:expr; ($($n0:ident),+) <- $e0:expr; $($tail:tt)+) => { ... };
    (@process ($($move:tt)*) ($($n:ident),+) <- $e:expr; ($($n0:ident),+) = $e0:expr; $($tail:tt)+) => { ... };
    (@process ($($move:tt)*) ($($n:ident),+) <- $e:expr; $n0:ident <- $e0:expr; $($tail:tt)+) => { ... };
    (@process ($($move:tt)*) ($($n:ident),+) <- $e:expr; $n0:ident = $e0:expr; $($tail:tt)+) => { ... };
    (@process ($($move:tt)*) $n:ident <- $e:expr; => $e0:expr) => { ... };
    (@process ($($move:tt)*) $n:ident <- $e:expr; if $g:expr; $($tail:tt)+) => { ... };
    (@process ($($move:tt)*) $n:ident <- $e:expr; ($($n0:ident),+) <- $e0:expr; $($tail:tt)+) => { ... };
    (@process ($($move:tt)*) $n:ident <- $e:expr; ($($n0:ident),+) = $e0:expr; $($tail:tt)+) => { ... };
    (@process ($($move:tt)*) $n:ident <- $e:expr; $n0:ident <- $e0:expr; $($tail:tt)+) => { ... };
    (@process ($($move:tt)*) $n:ident <- $e:expr; $n0:ident = $e0:expr; $($tail:tt)+) => { ... };
    (move; $($tail:tt)+) => { ... };
    ($($tail:tt)+) => { ... };
}

Scala-like for comprehension similar to those described in https://stackoverflow.com/questions/3754089/scala-for-comprehension#3754568 The main difference is that since rust does not have partial functions, we do not support general patterns in the left-hand sides, but only single identifiers and list of identifiers (that will match a tuple).

The macro will work for any type that implements the map, flat_map and filter functions.

Examples

Basic usage

Mappings are defined using the <- operator and the final value is declared with the => operator:

let l = map_for!{
   x <- 0..4;
   y <- 0..x;
   => y
   // y will take the following values depending on x:
   // x == 0 -> empty
   // x == 1 -> 0
   // x == 2 -> 0, 1
   // x == 3 -> 0, 1, 2
}.collect::<Vec<_>>();
assert_eq!(l, vec![ 0, 0, 1, 0, 1, 2 ]);

Moving or borrowing

By default, the generated closures will borrow their environment. If you'd prefer the environment to be moved inside the closures, you can specify move; as the first statement (the semicolon is here to make it clear that this will be applied globally to all the closures generated by the macro).

let l = map_for!{
   move;    // Only for illustration, not strictly necessary here
   x <- 0..4;
   => x
}.collect::<Vec<_>>();
assert_eq!(l, vec![ 0, 1, 2, 3 ]);

Regular binding

Regular bindings (i.e. direct bindings that don't require a call to map or flat_map) can be inserted between mappings by using the = operator:

let l = map_for!{
   move;
   x <- 0..4;
   y = 2*x;     // This is directly translated into `let y = 2*x;`
                // without going through a mapping function.
   z <- 0..1;
   => y+z
}.collect::<Vec<_>>();
assert_eq!(l, vec![ 0, 2, 4, 6 ]);

Tuple deconstruction

A limited form of pattern-matching is available to deconstruct single-level tuples:

use map_for::FlatMap;   // Required to use an `Option`
let e = map_for!{
   (a, b) <- Some ((1, 2));
   (c, d) = (3, 4);
   => a+b+c+d };
assert_eq!(e, Some (10));

Filtering

Mappings can be filtered using an if statement:

let l = map_for!{
   x <- 0..10;
   if (x%2) == 0;
   => x }.collect::<Vec<_>>();
assert_eq!(l, vec![ 0, 2, 4, 6, 8 ]);

Custom types

Except for filtering, map_for will work with any type that defines map and flat_map either directly or because it implements a trait that defines those fuctions. Moreover filtering will work with any type that also defines a filter function:

#[derive (Debug, PartialEq)]
enum MyMonad {
   Empty,
   Value (i32),
}
impl MyMonad {
   fn flat_map<F> (self, f: F) -> Self
      where F: FnOnce (i32) -> Self
   {
      use MyMonad::*;
      match self {
         Empty => Empty,
         Value (x) => f (x),
      }
   }

   fn map<F> (self, f: F) -> Self
      where F: FnOnce (i32) -> i32
   {
      use MyMonad::*;
      match self {
         Empty => Empty,
         Value (x) => Value (f (x)),
      }
   }

   fn filter<P> (self, p: P) -> Self
      where P: FnOnce (i32) -> bool
   {    // Only required to enable the `if` statement in `map_for`
      use MyMonad::*;
      match self {
         Value (x) if p (x) => self,
         _ => Empty,
      }
   }
}
use MyMonad::*;
let c = map_for!{
   a <- Value (1);
   if (a == 1);
   b <- Value (2);
   => a+b };
assert_eq!(c, Value (3));