use std::fmt::Debug;
pub struct Reader<E, A> {
f: Box<dyn Fn(&E) -> A>,
}
impl<E: 'static, A: 'static> Reader<E, A> {
pub fn new(f: impl Fn(&E) -> A + 'static) -> Self {
Self { f: Box::new(f) }
}
pub fn pure(a: A) -> Self
where
A: Clone,
{
Self {
f: Box::new(move |_| a.clone()),
}
}
pub fn run(&self, env: &E) -> A {
(self.f)(env)
}
pub fn map<B: 'static>(self, g: impl Fn(A) -> B + 'static) -> Reader<E, B> {
Reader::new(move |env| g((self.f)(env)))
}
pub fn bind<B: 'static>(self, g: impl Fn(A) -> Reader<E, B> + 'static) -> Reader<E, B> {
Reader::new(move |env| {
let a = (self.f)(env);
g(a).run(env)
})
}
pub fn ask() -> Reader<E, E>
where
E: Clone,
{
Reader::new(|env: &E| env.clone())
}
}
impl<E, A: Debug> Debug for Reader<E, A> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Reader<_, _>")
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn left_identity() {
let a = 42;
let f = |x: i32| Reader::new(move |env: &i32| x + env);
let left = Reader::<i32, i32>::pure(a).bind(f);
let right = (|x: i32| Reader::new(move |env: &i32| x + env))(a);
assert_eq!(left.run(&10), right.run(&10));
}
#[test]
fn right_identity() {
let m = Reader::new(|env: &i32| env * 2);
let result = Reader::new(|env: &i32| env * 2).bind(|a| Reader::<i32, i32>::pure(a));
assert_eq!(m.run(&21), result.run(&21));
}
#[test]
fn associativity() {
let m = Reader::new(|env: &i32| env + 1);
let f = |x: i32| Reader::new(move |env: &i32| x * env);
let g = |x: i32| Reader::new(move |_env: &i32| x + 100);
let left = Reader::new(|env: &i32| env + 1)
.bind(|x| Reader::new(move |env: &i32| x * env))
.bind(|x| Reader::new(move |_env: &i32| x + 100));
let right = Reader::new(|env: &i32| env + 1).bind(|x| {
(|x: i32| Reader::new(move |env: &i32| x * env))(x)
.bind(|y| Reader::new(move |_env: &i32| y + 100))
});
let _ = (m, f, g); assert_eq!(left.run(&10), right.run(&10));
}
#[test]
fn ask_returns_environment() {
let r = Reader::<String, String>::ask();
assert_eq!(r.run(&"hello".to_string()), "hello");
}
#[test]
fn map_transforms_output() {
let r = Reader::new(|x: &i32| x + 1).map(|y| y * 2);
assert_eq!(r.run(&20), 42);
}
#[test]
fn context_resolution_example() {
#[derive(Clone)]
struct Context {
therapeutic: bool,
}
let resolve = Reader::new(|ctx: &Context| {
if ctx.therapeutic {
"therapeutic_target"
} else {
"passive_homeostatic"
}
});
assert_eq!(
resolve.run(&Context { therapeutic: true }),
"therapeutic_target"
);
assert_eq!(
resolve.run(&Context { therapeutic: false }),
"passive_homeostatic"
);
}
}