use monadify::mdo;
use monadify::transformers::except::{Except, ExceptTKind};
use monadify::{Identity, IdentityKind};
#[derive(Clone, PartialEq, Debug)]
enum MathError {
DivByZero,
NegativeSqrt,
}
type Checked<A> = Except<MathError, A>;
type CKind = ExceptTKind<MathError, IdentityKind>;
fn safe_div(a: f64, b: f64) -> Checked<f64> {
if b == 0.0 {
Checked::throw(MathError::DivByZero)
} else {
Checked::ok(a / b)
}
}
fn safe_sqrt(x: f64) -> Checked<f64> {
if x < 0.0 {
Checked::throw(MathError::NegativeSqrt)
} else {
Checked::ok(x.sqrt())
}
}
fn main() {
println!("=== Except monad: safe calculator with error recovery ===\n");
println!("Test 1: safe_div(10.0, 2.0) — success path");
let Identity(res1) = safe_div(10.0, 2.0).run_except_t;
assert_eq!(res1, Ok(5.0), "expected Ok(5.0), got {:?}", res1);
println!(" {:?} PASSED\n", res1);
println!("Test 2: safe_div(1.0, 0.0) — divide-by-zero error");
let Identity(res2) = safe_div(1.0, 0.0).run_except_t;
assert_eq!(
res2,
Err(MathError::DivByZero),
"expected Err(DivByZero), got {:?}",
res2
);
println!(" {:?} PASSED\n", res2);
println!("Test 3: safe_sqrt(-4.0) — negative-sqrt error");
let Identity(res3) = safe_sqrt(-4.0).run_except_t;
assert_eq!(
res3,
Err(MathError::NegativeSqrt),
"expected Err(NegativeSqrt), got {:?}",
res3
);
println!(" {:?} PASSED\n", res3);
println!("Test 4: safe_div(1.0, 0.0).catch(|_| Checked::ok(0.0)) — recovery");
let recovered = safe_div(1.0, 0.0).catch(|_| Checked::ok(0.0));
let Identity(res4) = recovered.run_except_t;
assert_eq!(res4, Ok(0.0), "expected Ok(0.0), got {:?}", res4);
println!(" {:?} PASSED\n", res4);
println!("Test 5: safe_div(10.0, 2.0).catch(handler) — Ok passes through");
let passthrough = safe_div(10.0, 2.0).catch(|_e| {
Checked::ok(-1.0)
});
let Identity(res5) = passthrough.run_except_t;
assert_eq!(
res5,
Ok(5.0),
"expected Ok(5.0) (handler must not fire), got {:?}",
res5
);
println!(" {:?} PASSED\n", res5);
println!("Test 6: mdo! — sqrt(safe_div(16.0, 4.0)) == sqrt(4.0) == 2.0");
let chained = mdo! {
CKind;
x <- safe_div(16.0, 4.0);
y <- safe_sqrt(x);
pure(y)
};
let Identity(res6) = chained.run_except_t;
assert_eq!(res6, Ok(2.0), "expected Ok(2.0), got {:?}", res6);
println!(" {:?} PASSED\n", res6);
println!("Test 7: mdo! — DivByZero short-circuits, sqrt step never runs");
let short_circuit = mdo! {
CKind;
x <- safe_div(1.0, 0.0);
y <- safe_sqrt(x);
pure(y)
};
let Identity(res7) = short_circuit.run_except_t;
assert_eq!(
res7,
Err(MathError::DivByZero),
"expected Err(DivByZero) (short-circuit), got {:?}",
res7
);
println!(" {:?} PASSED\n", res7);
println!("=== All tests passed! ===");
println!("\nKey insight:");
println!(" * throw injects an error and short-circuits the computation.");
println!(" * ok lifts a value onto the success branch.");
println!(" * catch intercepts Err and runs a recovery handler;");
println!(" Ok values pass through with the handler never called.");
println!(" * mdo! sequences steps via do-notation; an Err in any step");
println!(" short-circuits the rest — the continuation never runs.");
}