use monadify::identity::{Identity, IdentityKind};
use monadify::mdo;
use monadify::transformers::reader::{MonadReader, Reader, ReaderT, ReaderTKind};
#[derive(Clone, Debug, PartialEq)]
struct AppConfig {
base_rate: f64,
scaling_factor: f64,
}
type ConfigReader<A> = Reader<AppConfig, A>;
type ReaderKind = ReaderTKind<AppConfig, IdentityKind>;
fn run_reader<A>(comp: &ConfigReader<A>, cfg: AppConfig) -> A {
let Identity(value) = (comp.run_reader_t)(cfg);
value
}
fn get_base_rate() -> ConfigReader<f64> {
ReaderT::new(|cfg: AppConfig| Identity(cfg.base_rate))
}
fn get_scaling_factor() -> ConfigReader<f64> {
ReaderT::new(|cfg: AppConfig| Identity(cfg.scaling_factor))
}
fn compute_adjusted_rate(base: f64, factor: f64) -> ConfigReader<f64> {
ReaderT::new(move |_cfg: AppConfig| {
Identity(base * factor)
})
}
fn ask_config() -> ConfigReader<AppConfig> {
<ReaderKind as MonadReader<AppConfig, AppConfig, IdentityKind>>::ask()
}
fn compute_pricing() -> ConfigReader<String> {
mdo! {
ReaderKind;
base <- get_base_rate();
factor <- get_scaling_factor();
adjusted <- compute_adjusted_rate(base, factor);
let label = format!("Base: {:.2}, Factor: {:.2}, Adjusted: {:.2}", base, factor, adjusted);
pure(label)
}
}
fn compute_pricing_with_ask() -> ConfigReader<String> {
mdo! {
ReaderKind;
cfg <- ask_config();
let adjusted = cfg.base_rate * cfg.scaling_factor;
let label = format!(
"Config-based: Base={:.2}, Factor={:.2}, Adjusted={:.2}",
cfg.base_rate, cfg.scaling_factor, adjusted
);
pure(label)
}
}
fn multi_step_pricing() -> ConfigReader<(f64, f64, f64)> {
mdo! {
ReaderKind;
base <- get_base_rate();
factor <- get_scaling_factor();
adjusted <- compute_adjusted_rate(base, factor);
pure((base, factor, adjusted))
}
}
fn main() {
println!("=== Do-notation with ReaderT: Environment Threading Example ===\n");
let config1 = AppConfig {
base_rate: 10.0,
scaling_factor: 1.5,
};
let config2 = AppConfig {
base_rate: 20.0,
scaling_factor: 2.0,
};
let config3 = AppConfig {
base_rate: 5.5,
scaling_factor: 3.2,
};
println!("Test 1: Basic do-notation computation");
let computation = compute_pricing();
let result1 = run_reader(&computation, config1.clone());
println!(" Config: {:?}", config1);
println!(" Result: {}", result1);
assert_eq!(result1, "Base: 10.00, Factor: 1.50, Adjusted: 15.00");
println!(" ✓ PASSED\n");
println!("Test 2: Same computation against different config");
let result2 = run_reader(&computation, config2.clone());
println!(" Config: {:?}", config2);
println!(" Result: {}", result2);
assert_eq!(result2, "Base: 20.00, Factor: 2.00, Adjusted: 40.00");
println!(" ✓ PASSED\n");
println!("Test 3: Third configuration");
let result3 = run_reader(&computation, config3.clone());
println!(" Config: {:?}", config3);
println!(" Result: {}", result3);
assert_eq!(result3, "Base: 5.50, Factor: 3.20, Adjusted: 17.60");
println!(" ✓ PASSED\n");
println!("Test 4: Using `ask()` to access the full environment");
let computation_with_ask = compute_pricing_with_ask();
let result4 = run_reader(&computation_with_ask, config1.clone());
println!(" Config: {:?}", config1);
println!(" Result: {}", result4);
assert_eq!(
result4,
"Config-based: Base=10.00, Factor=1.50, Adjusted=15.00"
);
println!(" ✓ PASSED\n");
println!("Test 5: Multi-step computation (3 bindings)");
let multi_comp = multi_step_pricing();
let (base, factor, adjusted) = run_reader(&multi_comp, config2.clone());
println!(" Config: {:?}", config2);
println!(" Base: {:.2}", base);
println!(" Factor: {:.2}", factor);
println!(" Adjusted (base * factor): {:.2}", adjusted);
assert_eq!(base, 20.0);
assert_eq!(factor, 2.0);
assert_eq!(adjusted, 40.0);
println!(" ✓ PASSED\n");
println!("=== All tests passed! ===");
println!("\nKey insight: `mdo!` with `ReaderT` demonstrates the power of monadic threading:");
println!(" • Each step (get_base_rate, get_scaling_factor, etc.) silently receives");
println!(" the same AppConfig from the environment.");
println!(" • No explicit parameter passing required — the do-notation macro desugars");
println!(" to nested `bind` calls that thread the environment automatically.");
println!(" • This is especially powerful for dependency injection, configuration");
println!(" management, and multi-step computations in complex applications.");
}