raskell 0.1.1

Haskell-style functional programming for Rust
Documentation
//! Evaluation order, ownership and degenerate blocks.

use std::cell::Cell;

use raskell::hdo;

#[derive(Debug, PartialEq)]
enum Error {
    Failed,
    Guarded,
}

#[test]
fn nothing_after_a_none_is_evaluated() {
    let calls = Cell::new(0);

    let bump = |value: Option<i32>| {
        calls.set(calls.get() + 1);
        value
    };

    let result = hdo! {
        x <- bump(None);
        y <- bump(Some(1));

        pure x + y
    };

    assert_eq!(result, None);
    assert_eq!(calls.get(), 1);
}

#[test]
fn nothing_after_an_err_is_evaluated() {
    let calls = Cell::new(0);

    let bump = |value: Result<i32, Error>| {
        calls.set(calls.get() + 1);
        value
    };

    let result: Result<i32, Error> = hdo! {
        x <- bump(Err(Error::Failed));
        y <- bump(Ok(1));

        pure x + y
    };

    assert_eq!(result, Err(Error::Failed));
    assert_eq!(calls.get(), 1);
}

#[test]
fn a_failed_guard_stops_evaluation() {
    let calls = Cell::new(0);

    let bump = || {
        calls.set(calls.get() + 1);
        Some(1)
    };

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

        guard x > 50;

        y <- bump();

        pure x + y
    };

    assert_eq!(result, None);
    assert_eq!(calls.get(), 0);
}

#[test]
fn a_thrown_guard_stops_evaluation() {
    let calls = Cell::new(0);

    let bump = || {
        calls.set(calls.get() + 1);
        Ok(1)
    };

    let result: Result<i32, Error> = hdo! {
        x <- Ok(10);

        guard x > 50 throw Error::Guarded;

        y <- bump();

        pure x + y
    };

    assert_eq!(result, Err(Error::Guarded));
    assert_eq!(calls.get(), 0);
}

#[test]
fn an_empty_list_stops_evaluation() {
    let calls = Cell::new(0);

    let bump = || {
        calls.set(calls.get() + 1);
        vec![1]
    };

    let result: Vec<i32> = hdo! {
        x <- Vec::<i32>::new();
        y <- bump();

        pure x + y
    };

    assert_eq!(result, Vec::<i32>::new());
    assert_eq!(calls.get(), 0);
}

#[test]
fn a_list_bind_evaluates_the_rest_once_per_element() {
    let calls = Cell::new(0);

    let bump = || {
        calls.set(calls.get() + 1);
        vec![1, 2]
    };

    let result: Vec<i32> = hdo! {
        x <- vec![10, 20, 30];
        y <- bump();

        pure x + y
    };

    assert_eq!(result, vec![11, 12, 21, 22, 31, 32]);
    assert_eq!(calls.get(), 3);
}

#[test]
fn non_copy_values_move_through_the_block() {
    let result = hdo! {
        name <- Some(String::from("meetzli"));
        upper <- Some(name.to_uppercase());

        let joined = format!("{name}/{upper}");

        pure joined
    };

    assert_eq!(result.as_deref(), Some("meetzli/MEETZLI"));
}

#[test]
fn a_block_without_binds_is_a_plain_value() {
    let result = hdo! {
        let x = 10;

        pure x * 2
    };

    assert_eq!(result, 20);
}

#[test]
fn a_block_with_only_pure_is_a_plain_value() {
    let result = hdo! {
        pure 20
    };

    assert_eq!(result, 20);
}

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

        pure x
    };

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

#[test]
fn a_leading_guard_short_circuits() {
    let result: Option<i32> = hdo! {
        guard false;

        x <- Some(10);

        pure x
    };

    assert_eq!(result, None);
}

#[test]
fn blocks_nest() {
    let result = hdo! {
        x <- Some(1);
        y <- hdo! {
            z <- Some(2);

            pure z * 10
        };

        pure x + y
    };

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

#[test]
fn user_bindings_named_like_the_expansion_are_untouched() {
    let __raskell_value = 100;
    let __raskell_error = 200;

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

        pure x + __raskell_value + __raskell_error
    };

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

#[test]
fn blocks_work_inside_generic_functions() {
    fn double<T>(value: Option<T>) -> Option<(T, usize)>
    where
        T: Clone,
    {
        hdo! {
            first <- value;

            let pair = (first.clone(), 1usize);

            pure pair
        }
    }

    assert_eq!(double(Some('a')), Some(('a', 1)));
    assert_eq!(double::<char>(None), None);
}