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);
}