raskell 0.1.0

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

#[derive(Debug, PartialEq)]
enum UserError {
    Inactive,
    PatternMismatch,
}

#[test]
fn option_bind() {
    let result = hdo! {
        x <- Some(10);
        y <- Some(20);

        pure x + y
    };

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

#[test]
fn option_short_circuits_on_none() {
    let result = hdo! {
        x <- Some(10);
        y <- None::<i32>;

        pure x + y
    };

    assert_eq!(result, None);
}

#[test]
fn let_bindings_work() {
    let result = hdo! {
        x <- Some(10);

        let y = x * 2;
        let z = y + 5;

        pure z
    };

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

#[test]
fn option_guard_passes() {
    let result = hdo! {
        x <- Some(10);

        guard x > 5;

        pure x * 2
    };

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

#[test]
fn option_guard_stops() {
    let result = hdo! {
        x <- Some(10);

        guard x > 50;

        pure x * 2
    };

    assert_eq!(result, None);
}

#[test]
fn result_bind() {
    let result: Result<i32, UserError> = hdo! {
        x <- Ok(10);
        y <- Ok(20);

        pure x + y
    };

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

#[test]
fn result_guard_passes() {
    let result: Result<i32, UserError> = hdo! {
        x <- Ok(10);

        guard x > 5 throw UserError::Inactive;

        pure x * 2
    };

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

#[test]
fn result_guard_throws() {
    let result: Result<i32, UserError> = hdo! {
        x <- Ok(10);

        guard x > 50 throw UserError::Inactive;

        pure x * 2
    };

    assert_eq!(result, Err(UserError::Inactive));
}

#[test]
fn ordinary_rust_statements_work() {
    let result = hdo! {
        x <- Some(10);

        let mut y = x;
        y += 5;

        pure y
    };

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

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

fn fail() -> Result<(), ActionError> {
    Err(ActionError::Boom)
}

#[test]
fn bare_result_action_short_circuits() {
    let result: Result<i32, ActionError> = hdo! {
        x <- Ok(10);

        fail();

        pure x
    };

    assert_eq!(result, Err(ActionError::Boom));
}

#[test]
fn option_refutable_pattern_matches() {
    let result = hdo! {
        Some(x) <- Some(Some(10));

        pure x * 2
    };

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

#[test]
fn option_refutable_pattern_fails() {
    let result = hdo! {
        Some(x) <- Some(None::<i32>);

        pure x * 2
    };

    assert_eq!(result, None);
}

#[test]
fn result_refutable_pattern_matches() {
    let result: Result<i32, UserError> = hdo! {
        Some(x) <- Ok(Some(10)) throw UserError::Inactive;

        pure x * 2
    };

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

#[test]
fn result_refutable_pattern_mismatch_throws() {
    let result: Result<i32, UserError> = hdo! {
        Some(x) <- Ok(None::<i32>) throw UserError::Inactive;

        pure x * 2
    };

    assert_eq!(result, Err(UserError::Inactive));
}

#[test]
fn result_refutable_pattern_preserves_error() {
    let failed: Result<Option<i32>, UserError> = Err(UserError::Inactive);

    let result = hdo! {
        Some(x) <- failed throw UserError::PatternMismatch;

        pure x * 2
    };

    assert_eq!(result, Err(UserError::Inactive));
}

#[test]
fn bind_type_ascription_pins_untyped_expression() {
    let result: Result<i32, UserError> = hdo! {
        Some(x): Option<i32> <- Err(UserError::Inactive) throw UserError::PatternMismatch;

        pure x * 2
    };

    assert_eq!(result, Err(UserError::Inactive));
}

#[test]
fn bind_type_ascription_on_irrefutable_pattern() {
    let result = hdo! {
        x: u8 <- Some(10);

        pure x * 2
    };

    assert_eq!(result, Some(20u8));
}

#[test]
fn bind_type_ascription_on_option_pattern() {
    let result = hdo! {
        Some(x): Option<u8> <- Some(Some(10));

        pure x * 2
    };

    assert_eq!(result, Some(20u8));
}

#[test]
fn let_type_ascription_works() {
    let result = hdo! {
        x <- Some(10);

        let y: i64 = i64::from(x) * 2;

        pure y
    };

    assert_eq!(result, Some(20i64));
}

#[test]
fn bind_type_ascription_with_nested_generics() {
    let result: Result<usize, UserError> = hdo! {
        Some(items): Option<Vec<Result<i32, UserError>>>
            <- Ok(Some(vec![Ok(1), Err(UserError::Inactive)]))
            throw UserError::PatternMismatch;

        pure items.len()
    };

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

#[test]
fn bind_type_ascription_on_reference() {
    let value = 10i32;

    let result = hdo! {
        x: &i32 <- Some(&value);

        pure *x * 2
    };

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

#[test]
fn bind_type_ascription_on_tuple() {
    let result = hdo! {
        (a, b): (u8, u8) <- Some((1, 2));

        pure a + b
    };

    assert_eq!(result, Some(3u8));
}

#[test]
fn multiple_typed_binds_chain() {
    let result: Result<i64, UserError> = hdo! {
        x: i64 <- Ok(10);
        Some(y): Option<i64> <- Ok(Some(20)) throw UserError::PatternMismatch;

        let z: i64 = x + y;

        pure z * 2
    };

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

#[test]
fn typed_bind_still_short_circuits_on_error() {
    let result: Result<i32, UserError> = hdo! {
        Some(x): Option<i32> <- Err(UserError::Inactive) throw UserError::PatternMismatch;

        pure x * 2
    };

    assert_eq!(result, Err(UserError::Inactive));
}

#[test]
fn typed_bind_throws_on_pattern_mismatch() {
    let result: Result<i32, UserError> = hdo! {
        Some(x): Option<i32> <- Ok(None) throw UserError::PatternMismatch;

        pure x * 2
    };

    assert_eq!(result, Err(UserError::PatternMismatch));
}

#[test]
fn typed_bind_without_ascription_still_works() {
    let result = hdo! {
        x <- Some(10);
        Some(y): Option<i32> <- Some(Some(20));

        pure x + y
    };

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