use monadify::identity::{Identity, IdentityKind};
use monadify::mdo;
use monadify::transformers::except::{ExceptT, ExceptTKind};
use monadify::transformers::reader::{ReaderT, ReaderTKind};
use monadify::transformers::state::StateTKind;
use monadify::transformers::writer::WriterTKind;
#[derive(Clone, PartialEq, Debug)]
enum EvalError {
UnboundVariable(String),
DivByZero,
}
#[derive(Clone, PartialEq, Debug)]
struct Env {
bindings: Vec<(String, i64)>,
}
impl Env {
fn new(bindings: &[(&str, i64)]) -> Self {
Env {
bindings: bindings.iter().map(|(k, v)| (k.to_string(), *v)).collect(),
}
}
fn lookup(&self, name: &str) -> Option<i64> {
self.bindings
.iter()
.find(|(k, _)| k == name)
.map(|(_, v)| *v)
}
}
type Log = Vec<String>;
#[derive(Clone, PartialEq, Debug)]
enum Expr {
Lit(i64),
Var(String),
Add(Box<Expr>, Box<Expr>),
Div(Box<Expr>, Box<Expr>),
}
type ExceptLayerKind = ExceptTKind<EvalError, IdentityKind>;
type WriterLayerKind = WriterTKind<Log, ExceptLayerKind>;
type StateLayerKind = StateTKind<i64, WriterLayerKind>;
type AppKind = ReaderTKind<Env, StateLayerKind>;
type App<A> = ReaderT<Env, StateLayerKind, A>;
fn ask_env() -> App<Env> {
AppKind::ask()
}
fn get_step() -> App<i64> {
AppKind::lift(StateLayerKind::get())
}
fn put_step(n: i64) -> App<()> {
AppKind::lift(StateLayerKind::put(n))
}
fn tell_log(msg: String) -> App<()> {
AppKind::lift(StateLayerKind::lift(WriterLayerKind::tell(vec![msg])))
}
fn throw_err(e: EvalError) -> App<i64> {
let thrown: ExceptT<EvalError, IdentityKind, i64> = ExceptT::throw(e);
AppKind::lift(StateLayerKind::lift(WriterLayerKind::lift(thrown)))
}
fn visit(label: String) -> App<()> {
mdo! { AppKind;
n <- get_step();
let msg = format!("step {}: {}", n + 1, label);
_ <- put_step(n + 1);
tell_log(msg.clone())
}
}
fn eval_owned(e: Expr) -> App<i64> {
eval(&e)
}
fn eval(expr: &Expr) -> App<i64> {
match expr {
Expr::Lit(n) => {
let n = *n;
mdo! { AppKind;
_ <- visit(format!("Lit({n})"));
pure(n)
}
}
Expr::Var(name) => {
let name = name.clone();
let label = format!("Var({name})");
mdo! { AppKind;
_ <- visit(label);
let lookup_key = name.clone();
let err = EvalError::UnboundVariable(name.clone());
env <- ask_env();
match env.lookup(&lookup_key) {
Some(v) => pure(v),
None => throw_err(err.clone()),
}
}
}
Expr::Add(l, r) => {
let l = (**l).clone();
let r = (**r).clone();
mdo! { AppKind;
lv <- eval_owned(l);
rv <- eval_owned(r.clone());
_ <- visit("Add".into());
pure(lv + rv)
}
}
Expr::Div(l, r) => {
let l = (**l).clone();
let r = (**r).clone();
mdo! { AppKind;
lv <- eval_owned(l);
rv <- eval_owned(r.clone());
_ <- visit("Div".into());
if rv == 0 { throw_err(EvalError::DivByZero) } else { pure(lv / rv) }
}
}
}
}
fn run(prog: App<i64>, env: Env, s0: i64) -> Result<((i64, i64), Log), EvalError> {
let state_layer = (prog.run_reader_t)(env);
let writer_layer = (state_layer.run_state_t)(s0);
let except_layer = writer_layer.run_writer_t;
let Identity(result) = except_layer.run_except_t;
result
}
fn main() {
println!("=== Four-deep stack: Reader+State+Writer+Except interpreter ===\n");
let env = Env::new(&[("x", 10), ("y", 4)]);
let expr = Expr::Add(
Box::new(Expr::Var("x".to_string())),
Box::new(Expr::Div(
Box::new(Expr::Lit(20)),
Box::new(Expr::Var("y".to_string())),
)),
);
let result = run(eval(&expr), env.clone(), 0);
let expected_trace = vec![
"step 1: Var(x)".to_string(),
"step 2: Lit(20)".to_string(),
"step 3: Var(y)".to_string(),
"step 4: Div".to_string(),
"step 5: Add".to_string(),
];
assert_eq!(
result,
Ok(((15, 5), expected_trace.clone())),
"success: value 15, 5 steps, full post-order trace"
);
println!("Success: x + (20 / y) with x=10, y=4");
println!(" result = {result:?}");
let ((value, steps), trace) = result.unwrap();
println!(" value = {value}");
println!(" step count = {steps}");
println!(" trace:");
for entry in &trace {
println!(" {entry}");
}
println!();
let env_unbound = Env::new(&[("x", 10)]);
let expr_unbound = Expr::Add(
Box::new(Expr::Var("x".to_string())),
Box::new(Expr::Var("z".to_string())),
);
let err_result = run(eval(&expr_unbound), env_unbound, 0);
assert_eq!(
err_result,
Err(EvalError::UnboundVariable("z".to_string())),
"unbound var: Err with NO ((value,state),log) — state+trace discarded"
);
assert!(
err_result.is_err(),
"result is a bare Err: the partial 2 steps and partial trace are gone"
);
println!("Error 1: x + z with x=10 (z unbound)");
println!(" result = {err_result:?}");
println!(" (state counter and trace are DISCARDED — Except is innermost)\n");
let env_div = Env::new(&[]);
let expr_div = Expr::Div(Box::new(Expr::Lit(1)), Box::new(Expr::Lit(0)));
let div_result = run(eval(&expr_div), env_div, 0);
assert_eq!(
div_result,
Err(EvalError::DivByZero),
"div-by-zero: Err with NO ((value,state),log) — state+trace discarded"
);
println!("Error 2: 1 / 0");
println!(" result = {div_result:?}");
println!(" (the 3 visited nodes' steps and trace are DISCARDED)\n");
println!("=== All assertions passed! ===");
println!();
println!("Key insight: layer ORDER decides error semantics. With Except");
println!("innermost, a throw collapses the whole stack to Err(e) — the");
println!("State counter and Writer trace live in the Ok branch and vanish.");
println!("Swapping Except above Writer/State would instead preserve them.");
}