raskell 0.1.0

Haskell-style functional programming for Rust
Documentation
use raskell::hdo;

#[derive(Debug, PartialEq)]
enum Inner {
    Boom,
}

#[derive(Debug, PartialEq)]
enum Outer {
    Wrapped,
    Missing,
}

impl From<Inner> for Outer {
    fn from(_: Inner) -> Self {
        Outer::Wrapped
    }
}

fn inner_ok() -> Result<i32, Inner> {
    Ok(10)
}

fn inner_err() -> Result<i32, Inner> {
    Err(Inner::Boom)
}

#[test]
fn try_bind_converts_error() {
    let result: Result<i32, Outer> = hdo! {
        x <-? inner_err();

        pure x * 2
    };

    assert_eq!(result, Err(Outer::Wrapped));
}

#[test]
fn try_bind_passes_value_through() {
    let result: Result<i32, Outer> = hdo! {
        x <-? inner_ok();

        pure x * 2
    };

    assert_eq!(result, Ok(20));
}

#[test]
fn try_bind_nested_in_plain_bind() {
    let result: Result<i32, Outer> = hdo! {
        a <- Ok(1);
        b <-? inner_err();

        pure a + b
    };

    assert_eq!(result, Err(Outer::Wrapped));
}

#[test]
fn try_bind_with_refutable_pattern() {
    fn maybe() -> Result<Option<i32>, Inner> {
        Ok(None)
    }

    let result: Result<i32, Outer> = hdo! {
        Some(x) <-? maybe() throw Outer::Missing;

        pure x
    };

    assert_eq!(result, Err(Outer::Missing));
}

#[test]
fn try_bind_with_ascription() {
    let result: Result<i32, Outer> = hdo! {
        Some(x): Option<i32> <-? Err(Inner::Boom) throw Outer::Missing;

        pure x
    };

    assert_eq!(result, Err(Outer::Wrapped));
}

#[test]
fn list_bind_is_a_cartesian_product() {
    let result: Vec<(i32, char)> = hdo! {
        x <- vec![1, 2];
        y <- vec!['a', 'b'];

        pure (x, y)
    };

    assert_eq!(result, vec![(1, 'a'), (1, 'b'), (2, 'a'), (2, 'b')]);
}

#[test]
fn list_guard_filters() {
    let result: Vec<i32> = hdo! {
        x <- vec![1, 2, 3, 4];

        guard x % 2 == 0;

        pure x * 10
    };

    assert_eq!(result, vec![20, 40]);
}

#[test]
fn list_let_and_nested_guard() {
    let result: Vec<i32> = hdo! {
        x <- vec![1, 2, 3];
        y <- vec![10, 20];

        let sum = x + y;

        guard sum > 12;

        pure sum
    };

    assert_eq!(result, vec![21, 22, 13, 23]);
}

#[test]
fn empty_list_short_circuits() {
    let result: Vec<i32> = hdo! {
        x <- vec![1, 2];
        y <- Vec::<i32>::new();

        pure x + y
    };

    assert_eq!(result, Vec::<i32>::new());
}

#[test]
fn statement_expression_semantics_are_monadic() {
    // Documented behaviour: a bare statement expression is an *action*, so an
    // `Option` in statement position short-circuits the block.
    let mut values = vec![1];

    let result = hdo! {
        x <- Some(10);

        values.pop();

        pure x
    };

    assert_eq!(result, Some(10));

    let mut empty: Vec<i32> = Vec::new();

    let short_circuited = hdo! {
        x <- Some(10);

        empty.pop();

        pure x
    };

    assert_eq!(short_circuited, None);
}

#[test]
fn discarding_an_action_keeps_it_out_of_the_chain() {
    let mut empty: Vec<i32> = Vec::new();

    let result = hdo! {
        x <- Some(10);

        let _ = empty.pop();

        pure x
    };

    assert_eq!(result, Some(10));
}