use monadify::identity::{Identity, IdentityKind};
use monadify::mdo;
use monadify::transformers::state::{State, StateTKind};
type Stack<A> = State<Vec<i64>, A>;
type SKind = StateTKind<Vec<i64>, IdentityKind>;
fn push(n: i64) -> Stack<()> {
SKind::modify(move |mut st| {
st.push(n);
st
})
}
fn binary_op(op: impl Fn(i64, i64) -> i64 + 'static) -> Stack<()> {
SKind::state(move |mut st| {
let b = st.pop().expect("stack underflow: missing right operand");
let a = st.pop().expect("stack underflow: missing left operand");
st.push(op(a, b));
((), st)
})
}
fn add_op() -> Stack<()> {
binary_op(|a, b| a + b)
}
fn mul_op() -> Stack<()> {
binary_op(|a, b| a * b)
}
fn peek_top() -> Stack<i64> {
SKind::gets(|st| *st.last().expect("peek_top: empty stack"))
}
fn prog_add() -> Stack<()> {
mdo! {
SKind;
_ <- push(3);
_ <- push(4);
_ <- add_op();
pure(())
}
}
fn prog_full() -> Stack<(i64, i64)> {
mdo! {
SKind;
_ <- push(3);
_ <- push(4);
_ <- add_op();
mid <- peek_top(); _ <- push(5);
_ <- mul_op();
result <- peek_top(); pure((mid, result))
}
}
fn main() {
println!("=== Do-notation with StateT: RPN Stack Calculator ===\n");
println!("Test 1: Intermediate check — `3 4 +`");
let Identity(stack_after_add) = prog_add().exec_state_t(vec![]);
let top_after_add = *stack_after_add.last().expect("stack must be non-empty");
assert_eq!(top_after_add, 7, "stack top after `3 4 +` must be 7");
println!(" Stack after `3 4 +`: {:?}", stack_after_add);
println!(" Stack top = {} (expected 7)", top_after_add);
println!(" PASSED\n");
println!("Test 2: Full program `3 4 + 5 *` (expected 35)");
let Identity(((mid, result), final_stack)) = (prog_full().run_state_t)(vec![]);
assert_eq!(mid, 7, "intermediate value after `3 4 +` must be 7");
assert_eq!(result, 35, "`3 4 + 5 *` must evaluate to 35");
assert_eq!(final_stack, vec![35i64], "final stack must be exactly [35]");
println!(" Intermediate sum (3+4) captured mid-program: {}", mid);
println!(" Final product (7*5): {}", result);
println!(" Final stack: {:?}", final_stack);
println!(" PASSED\n");
println!("Test 3: Runners — eval_state_t and exec_state_t");
let Identity(value_only) = prog_full().eval_state_t(vec![]);
let Identity(state_only) = prog_full().exec_state_t(vec![]);
assert_eq!(
value_only,
(7, 35),
"eval_state_t must return (mid=7, result=35)"
);
assert_eq!(
state_only,
vec![35i64],
"exec_state_t must return final stack [35]"
);
println!(" eval_state_t -> {:?}", value_only);
println!(" exec_state_t -> {:?}", state_only);
println!(" PASSED\n");
println!("=== All tests passed! ===");
println!("\nKey insight: `mdo!` with `StateT` threads the operand stack implicitly:");
println!(" * `modify` handles push: transforms the state without yielding a value.");
println!(" * `state` handles binary ops: pops two operands, pushes one result.");
println!(" * `gets` peeks at the top of the stack (read-only state projection).");
println!(" * `run_state_t` returns both the produced value and the final state.");
println!(" * No explicit stack is threaded through any function argument.");
}