Macro rust2fun::bind

source ·
macro_rules! bind {
    (return $e:expr, if $cond:expr;) => { ... };
    (return $e:expr;) => { ... };
    (let $x:ident : $t:ty  = $e:expr; $($rest:tt)+) => { ... };
    (let $p:pat = $e:expr; $($rest:tt)+) => { ... };
    (for $p:pat in $e:expr , if $cond:expr ; $($rest:tt)+) => { ... };
    (for $p:pat in $e:expr; $($rest:tt)+) => { ... };
    ($s:stmt;  $($rest:tt)+) => { ... };
    ($e:expr) => { ... };
}
Expand description

Bind macro. Allows for a more natural syntax for monadic composition. It is similar to the do notation in Haskell or the for notation in Scala.

Usage

use rust2fun::prelude::*;

let actual = bind! {
    for x in Some(1);
    for y in Some(2);
    x + y
};
assert_eq!(Some(3), actual);

let actual = bind! {
    for x in Some(1);
    for y in None::<i32>;
    x + y
};
assert_eq!(None, actual);

The syntax supports pattern matching, can bind variables and contain statements.

use rust2fun::prelude::*;

let actual = bind! {
    for (_, a) in Some((1, 2));
    let b = 3;
    std::println!("a = {}, b = {}", a, b);
    a + b
};

assert_eq!(Some(5), actual);

Guards can be implemented using an if statement within a bind discarding the result.

use rust2fun::prelude::*;

let actual = bind! {
    for x in Some(1);
    for _guard in if x > 0 { Some(()) } else { None };
    x
};

assert_eq!(Some(1), actual);

… or using the special if syntax for monoids.

use rust2fun::prelude::*;

let actual = bind! {
    for x in Some(1);
    for _guard in Some(()), if x > 0;
    x
};

assert_eq!(Some(1), actual);

The last example can be rewritten with the return syntax as follows:

use rust2fun::prelude::*;

let actual = bind! {
    for x in Some(1);
    return Some(x), if x > 0;
};

assert_eq!(Some(1), actual);

The return keyword is used to return the result of the last bind.

use rust2fun::prelude::*;

let actual = bind! {
    for x in Some(1);
    for y in Some(2);
    return Some(x + y);
};

assert_eq!(Some(3), actual);

Examples

use rust2fun::prelude::*;


fn get_opening_prices() -> Vec<(AssetId, i32)> {
    vec![(1, 225), (2, 310), (3, 128), (4, 99), (5, 200), (6, 0)]
}

fn get_closing_prices() -> Vec<(AssetId, i32)> {
   vec![(5, 210), (3, 130), (2, 308), (4, 100), (1, 220)]
}

fn get_asset_name(id: AssetId) -> Option<String> {
    match id {
        1 => Some("AAPL".to_string()),
        2 => Some("MSFT".to_string()),
        3 => Some("GOOG".to_string()),
        4 => Some("AMZN".to_string()),
        _ => None,
    }
}

let profits = bind! {
    for (id_open, opening_price) in get_opening_prices();
    for (id_close, closing_price) in get_closing_prices();
    let diff = closing_price - opening_price;
    for name in get_asset_name(id_open).into_iter().collect::<Vec<_>>(),
        if id_open == id_close && diff > 0;
    (name, diff)
};

assert_eq!(vec![("GOOG".to_string(), 2), ("AMZN".to_string(), 1)], profits);