Dependency Injection and Programmable Typeclasses

fx-rs unifies algebraic effects and dependency injection. Handlers are first-class values, not static typeclasses, so you can swap them at runtime and scope them to subcomputations.

Example: Swapping Handlers for a Subcomputation

#![allow(unused)]
fn main() {
use fx::Fx;

trait Logger { fn log(&self, msg: &str); }
struct ProdLogger;
impl Logger for ProdLogger { fn log(&self, msg: &str) { println!("prod: {}", msg); } }
struct TestLogger;
impl Logger for TestLogger { fn log(&self, msg: &str) { println!("test: {}", msg); } }

struct AppContext<L: Logger> { logger: L }

fn log_something<'f, L: Logger>(msg: &'f str) -> Fx<'f, L, ()> {
    Fx::pure(move |l: &L| l.log(msg))
}

let prod_ctx = AppContext { logger: ProdLogger };
let fx = log_something("main");
let _ = fx.run(&prod_ctx.logger);

// Swap to a test logger for a subcomputation
let test_ctx = AppContext { logger: TestLogger };
let _ = fx.adapt(|_| &test_ctx.logger, |c, r| (c, r)).run(&prod_ctx.logger);
}

This enables modular, testable, and flexible dependency management—without global singletons or static typeclasses.