use super::super::proptest_laws::{
arb_identity_i32, arb_linear_closure_params, arb_option_i32, arb_result_i32_string,
arb_vec_i32, linear_fn,
};
use monadify::applicative::kind::Applicative;
use monadify::identity::{Identity, IdentityKind};
use monadify::kind_based::kind::{OptionKind, ResultKind, VecKind};
use monadify::mdo;
use monadify::monad::kind::Bind;
use proptest::prelude::*;
proptest! {
#![proptest_config(ProptestConfig { cases: 256, ..ProptestConfig::default() })]
#[test]
fn option_mdo_law_left_identity(
a in any::<i32>(),
(ka, kb) in arb_linear_closure_params(),
k_present in any::<bool>(),
) {
let k = move |x: i32| -> Option<i32> {
if k_present {
let mut lf = linear_fn(ka, kb);
Some(lf(x))
} else {
None
}
};
let lhs: Option<i32> = mdo! {
OptionKind;
x <- OptionKind::pure(a);
k(x)
};
let rhs: Option<i32> = k(a);
prop_assert_eq!(lhs, rhs);
}
#[test]
fn option_mdo_law_right_identity(m in arb_option_i32()) {
let lhs: Option<i32> = mdo! {
OptionKind;
x <- m;
OptionKind::pure(x)
};
prop_assert_eq!(lhs, m);
}
#[test]
fn option_mdo_law_associativity(
m in arb_option_i32(),
(ka, kb) in arb_linear_closure_params(),
(ha, hb) in arb_linear_closure_params(),
k_present in any::<bool>(),
h_present in any::<bool>(),
) {
let k = move |x: i32| -> Option<i32> {
if k_present {
let mut lf = linear_fn(ka, kb);
Some(lf(x))
} else {
None
}
};
let h = move |y: i32| -> Option<i32> {
if h_present {
let mut lf = linear_fn(ha, hb);
Some(lf(y))
} else {
None
}
};
let lhs: Option<i32> = mdo! {
OptionKind;
x <- m;
y <- k(x);
h(y)
};
let rhs_nested: Option<i32> = mdo! {
OptionKind;
y <- mdo! {
OptionKind;
x <- m;
k(x)
};
h(y)
};
let rhs_hand: Option<i32> = OptionKind::bind(OptionKind::bind(m, k), h);
prop_assert_eq!(lhs, rhs_nested, "flat mdo! vs nested mdo!");
prop_assert_eq!(lhs, rhs_hand, "flat mdo! vs hand-written bind");
}
}
proptest! {
#![proptest_config(ProptestConfig { cases: 256, ..ProptestConfig::default() })]
#[test]
fn result_mdo_law_left_identity(
a in any::<i32>(),
(ka, kb) in arb_linear_closure_params(),
k_ok in any::<bool>(),
) {
let k = move |x: i32| -> Result<i32, String> {
if k_ok {
Ok(x.wrapping_mul(ka).wrapping_add(kb))
} else {
Err(String::from("k-err"))
}
};
let lhs: Result<i32, String> = mdo! {
ResultKind::<String>;
x <- ResultKind::<String>::pure(a);
k(x)
};
let rhs: Result<i32, String> = k(a);
prop_assert_eq!(lhs, rhs);
}
#[test]
fn result_mdo_law_right_identity(m in arb_result_i32_string()) {
let m_for_lhs = m.clone();
let lhs: Result<i32, String> = mdo! {
ResultKind::<String>;
x <- m_for_lhs;
ResultKind::<String>::pure(x)
};
prop_assert_eq!(lhs, m);
}
#[test]
fn result_mdo_law_associativity(
m in arb_result_i32_string(),
(ka, kb) in arb_linear_closure_params(),
(ha, hb) in arb_linear_closure_params(),
k_ok in any::<bool>(),
h_ok in any::<bool>(),
) {
let k = move |x: i32| -> Result<i32, String> {
if k_ok {
Ok(x.wrapping_mul(ka).wrapping_add(kb))
} else {
Err(String::from("k-err"))
}
};
let h = move |y: i32| -> Result<i32, String> {
if h_ok {
Ok(y.wrapping_mul(ha).wrapping_add(hb))
} else {
Err(String::from("h-err"))
}
};
let m_lhs = m.clone();
let m_rhs = m.clone();
let m_hand = m;
let lhs: Result<i32, String> = mdo! {
ResultKind::<String>;
x <- m_lhs;
y <- k(x);
h(y)
};
let rhs_nested: Result<i32, String> = mdo! {
ResultKind::<String>;
y <- mdo! {
ResultKind::<String>;
x <- m_rhs;
k(x)
};
h(y)
};
let rhs_hand: Result<i32, String> = ResultKind::<String>::bind(
ResultKind::<String>::bind(m_hand, k),
h,
);
prop_assert_eq!(lhs.clone(), rhs_nested, "flat mdo! vs nested mdo!");
prop_assert_eq!(lhs, rhs_hand, "flat mdo! vs hand-written bind");
}
}
proptest! {
#![proptest_config(ProptestConfig { cases: 256, ..ProptestConfig::default() })]
#[test]
fn vec_mdo_law_left_identity(
a in any::<i32>(),
(ka, kb) in arb_linear_closure_params(),
k_empty in any::<bool>(),
) {
let k = move |x: i32| -> Vec<i32> {
if k_empty {
Vec::new()
} else {
vec![x.wrapping_mul(ka).wrapping_add(kb)]
}
};
let lhs: Vec<i32> = mdo! {
VecKind;
x <- VecKind::pure(a);
k(x)
};
let rhs: Vec<i32> = k(a);
prop_assert_eq!(lhs, rhs);
}
#[test]
fn vec_mdo_law_right_identity(m in arb_vec_i32()) {
let m_for_lhs = m.clone();
let lhs: Vec<i32> = mdo! {
VecKind;
x <- m_for_lhs;
VecKind::pure(x)
};
prop_assert_eq!(lhs, m);
}
#[test]
fn vec_mdo_law_associativity(
m in arb_vec_i32(),
(ka, kb) in arb_linear_closure_params(),
(ha, hb) in arb_linear_closure_params(),
k_empty in any::<bool>(),
h_empty in any::<bool>(),
) {
let k = move |x: i32| -> Vec<i32> {
if k_empty {
Vec::new()
} else {
vec![x.wrapping_mul(ka).wrapping_add(kb)]
}
};
let h = move |y: i32| -> Vec<i32> {
if h_empty {
Vec::new()
} else {
vec![y.wrapping_mul(ha).wrapping_add(hb)]
}
};
let m_lhs = m.clone();
let m_rhs = m.clone();
let m_hand = m;
let lhs: Vec<i32> = mdo! {
VecKind;
x <- m_lhs;
y <- k(x);
h(y)
};
let rhs_nested: Vec<i32> = mdo! {
VecKind;
y <- mdo! {
VecKind;
x <- m_rhs;
k(x)
};
h(y)
};
let rhs_hand: Vec<i32> = VecKind::bind(VecKind::bind(m_hand, k), h);
prop_assert_eq!(lhs.clone(), rhs_nested, "flat mdo! vs nested mdo!");
prop_assert_eq!(lhs, rhs_hand, "flat mdo! vs hand-written bind");
}
}
proptest! {
#![proptest_config(ProptestConfig { cases: 256, ..ProptestConfig::default() })]
#[test]
fn identity_mdo_law_left_identity(
a in any::<i32>(),
(ka, kb) in arb_linear_closure_params(),
) {
let k = move |x: i32| -> Identity<i32> {
let mut lf = linear_fn(ka, kb);
Identity(lf(x))
};
let lhs: Identity<i32> = mdo! {
IdentityKind;
x <- IdentityKind::pure(a);
k(x)
};
let rhs: Identity<i32> = k(a);
prop_assert_eq!(lhs, rhs);
}
#[test]
fn identity_mdo_law_right_identity(m in arb_identity_i32()) {
let m_for_lhs = m.clone();
let lhs: Identity<i32> = mdo! {
IdentityKind;
x <- m_for_lhs;
IdentityKind::pure(x)
};
prop_assert_eq!(lhs, m);
}
#[test]
fn identity_mdo_law_associativity(
m in arb_identity_i32(),
(ka, kb) in arb_linear_closure_params(),
(ha, hb) in arb_linear_closure_params(),
) {
let k = move |x: i32| -> Identity<i32> {
let mut lf = linear_fn(ka, kb);
Identity(lf(x))
};
let h = move |y: i32| -> Identity<i32> {
let mut lf = linear_fn(ha, hb);
Identity(lf(y))
};
let m_lhs = m.clone();
let m_rhs = m.clone();
let m_hand = m;
let lhs: Identity<i32> = mdo! {
IdentityKind;
x <- m_lhs;
y <- k(x);
h(y)
};
let rhs_nested: Identity<i32> = mdo! {
IdentityKind;
y <- mdo! {
IdentityKind;
x <- m_rhs;
k(x)
};
h(y)
};
let rhs_hand: Identity<i32> = IdentityKind::bind(IdentityKind::bind(m_hand, k), h);
prop_assert_eq!(lhs.clone(), rhs_nested, "flat mdo! vs nested mdo!");
prop_assert_eq!(lhs, rhs_hand, "flat mdo! vs hand-written bind");
}
}
#[test]
fn hygiene_variable_shadowing_monadic_rebind() {
let result: Option<i32> = mdo! {
OptionKind;
x <- Some(5i32);
x <- Some(x * 2i32); OptionKind::pure(x) };
assert_eq!(result, Some(10));
}
#[test]
fn hygiene_variable_shadowing_let_binding() {
let result: Option<i32> = mdo! {
OptionKind;
x <- Some(7i32);
let x = x + 3i32; OptionKind::pure(x) };
assert_eq!(result, Some(10));
}
#[test]
fn hygiene_nested_do_block_option_in_option() {
let result: Option<i32> = mdo! {
OptionKind;
inner <- mdo! {
OptionKind;
a <- Some(3i32);
b <- Some(4i32);
OptionKind::pure(a * b) };
OptionKind::pure(inner + 1i32) };
assert_eq!(result, Some(13));
}
#[test]
fn hygiene_nested_do_block_inner_short_circuits() {
let result: Option<i32> = mdo! {
OptionKind;
inner <- mdo! {
OptionKind;
a <- Some(3i32);
b <- None::<i32>;
OptionKind::pure(a + b) };
OptionKind::pure(inner + 100i32) };
assert_eq!(result, None);
}
#[test]
fn hygiene_nested_do_block_vec_in_vec() {
let result: Vec<i32> = mdo! {
VecKind;
s <- mdo! {
VecKind;
a <- vec![1i32, 2];
b <- vec![10i32, 20];
VecKind::pure(a + b) };
VecKind::pure(s * 2i32) };
assert_eq!(result, vec![22, 42, 24, 44]);
}
#[test]
fn hygiene_guard_false_option_yields_none() {
let result: Option<i32> = mdo! {
OptionKind;
x <- Some(42i32);
guard(false);
OptionKind::pure(x)
};
assert_eq!(result, None);
}
#[test]
fn hygiene_guard_false_vec_yields_empty() {
let result: Vec<i32> = mdo! {
VecKind;
x <- vec![1i32, 2, 3];
guard(false);
VecKind::pure(x)
};
assert_eq!(result, Vec::<i32>::new());
}
#[test]
fn hygiene_evaluation_order_dependent_bindings() {
let result: Identity<i32> = mdo! {
IdentityKind;
x <- Identity(1i32);
y <- Identity(x + 1i32); z <- Identity(y + 1i32); IdentityKind::pure(x + y + z)
};
assert_eq!(result, Identity(6)); }
#[test]
fn hygiene_bare_expr_sequencing_runs_in_order() {
let result: Option<i32> = mdo! {
OptionKind;
x <- Some(10i32);
Some(()); y <- Some(x + 5i32); OptionKind::pure(y)
};
assert_eq!(result, Some(15));
}
#[test]
fn hygiene_let_stmt_runs_exactly_once_cell_counter() {
use std::cell::Cell;
use std::rc::Rc;
let counter = Rc::new(Cell::new(0i32));
let handle = Rc::clone(&counter);
let result: Option<i32> = mdo! {
OptionKind;
x <- Some(10i32);
let n = { counter.set(counter.get() + 1); counter.get() };
OptionKind::pure(x + n)
};
assert_eq!(result, Some(11));
assert_eq!(handle.get(), 1);
}
#[test]
fn hygiene_user_let_underscore_no_clash_with_macro_discard_pattern() {
let result: Option<i32> = mdo! {
OptionKind;
x <- Some(5i32);
Some(()); let _ = x * 100; OptionKind::pure(x * 2i32)
};
assert_eq!(result, Some(10));
}
#[test]
fn hygiene_tuple_pattern_bind_destructures_correctly() {
let result: Option<i32> = mdo! {
OptionKind;
(a, b) <- Some((3i32, 4i32));
OptionKind::pure(a + b)
};
assert_eq!(result, Some(7));
}