use monadify::applicative::kind::Applicative;
use monadify::identity::{Identity, IdentityKind};
use monadify::mdo;
use monadify::monad::kind::Bind;
use monadify::transformers::except::{Except, ExceptT, ExceptTKind};
use proptest::prelude::*;
type EKind = ExceptTKind<String, IdentityKind>;
type Checked<A> = Except<String, A>;
fn ok(n: i32) -> Checked<i32> {
ExceptT::ok(n)
}
fn boom(msg: &str) -> Checked<i32> {
ExceptT::throw(msg.to_string())
}
fn run_id<A>(m: Checked<A>) -> Result<A, String> {
let Identity(r) = m.run_except_t;
r
}
#[test]
fn except_mdo_happy_path() {
let comp: Checked<i32> = mdo! {
EKind;
x <- ok(1);
y <- ok(2);
z <- ok(3);
EKind::pure(x + y + z)
};
assert_eq!(run_id(comp), Ok(6));
}
#[test]
fn except_mdo_short_circuits_on_throw() {
let comp: Checked<i32> = mdo! {
EKind;
x <- ok(1);
_ <- boom("stop");
y <- ok(99); EKind::pure(x + y)
};
assert_eq!(run_id(comp), Err("stop".to_string()));
}
#[test]
fn except_mdo_equivalent_to_manual_bind() {
let via_mdo: Checked<i32> = mdo! {
EKind;
x <- ok(2);
_ <- boom("e");
EKind::pure(x)
};
let via_bind: Checked<i32> = EKind::bind(ok(2), move |x| {
EKind::bind(boom("e"), move |_| EKind::pure(x))
});
assert_eq!(run_id(via_mdo), run_id(via_bind));
}
proptest! {
#![proptest_config(ProptestConfig { cases: 256, ..ProptestConfig::default() })]
#[test]
fn except_mdo_matches_manual(a in any::<i32>(), b in any::<i32>(), throws in any::<bool>()) {
let mid = move || -> Checked<i32> {
if throws { boom("x") } else { ok(b) }
};
let via_mdo: Checked<i32> = mdo! {
EKind;
x <- ok(a);
y <- mid();
EKind::pure(x.wrapping_add(y))
};
let via_bind: Checked<i32> =
EKind::bind(ok(a), move |x| EKind::bind(mid(), move |y| EKind::pure(x.wrapping_add(y))));
prop_assert_eq!(run_id(via_mdo), run_id(via_bind));
}
}