use crate::foundation::function;
pub struct Reader<A, R> {
run: Box<dyn FnOnce(R) -> A>,
}
pub struct ReaderFn<A, R> {
run: Box<dyn Fn(&R) -> A>,
}
#[inline]
pub fn reader<A, R, F>(f: F) -> Reader<A, R>
where
F: FnOnce(R) -> A + 'static,
{
Reader { run: Box::new(f) }
}
#[inline]
pub fn reader_fn<A, R, F>(f: F) -> ReaderFn<A, R>
where
F: Fn(&R) -> A + 'static,
{
ReaderFn { run: Box::new(f) }
}
#[inline]
pub fn ask<R: 'static>() -> Reader<R, R> {
reader(function::identity)
}
#[inline]
pub fn pure<A: 'static, R: 'static>(a: A) -> Reader<A, R> {
reader(move |_| a)
}
#[inline]
pub fn succeed<A: 'static, R: 'static>(a: A) -> Reader<A, R> {
pure(a)
}
#[inline]
pub fn run<A, R>(r: Reader<A, R>, env: R) -> A {
(r.run)(env)
}
#[inline]
pub fn run_fn<A, R>(r: &ReaderFn<A, R>, env: &R) -> A {
(r.run)(env)
}
#[inline]
pub fn map<A, B, R, F>(r: Reader<A, R>, f: F) -> Reader<B, R>
where
A: 'static,
B: 'static,
R: 'static,
F: FnOnce(A) -> B + 'static,
{
reader(move |env| f(run(r, env)))
}
#[inline]
pub fn as_<A, B, R>(r: Reader<A, R>, b: B) -> Reader<B, R>
where
A: 'static,
B: 'static,
R: 'static,
{
map(r, move |_| b)
}
#[inline]
pub fn void<A: 'static, R: 'static>(r: Reader<A, R>) -> Reader<(), R> {
as_(r, ())
}
#[inline]
pub fn flat_map<A, B, R, F>(r: Reader<A, R>, f: F) -> Reader<B, R>
where
A: 'static,
B: 'static,
R: Clone + 'static,
F: FnOnce(A) -> Reader<B, R> + 'static,
{
reader(move |env: R| {
let a = run(r, env.clone());
run(f(a), env)
})
}
#[inline]
pub fn flatten<A, R>(rr: Reader<Reader<A, R>, R>) -> Reader<A, R>
where
A: 'static,
R: Clone + 'static,
{
flat_map(rr, |r| r)
}
#[inline]
pub fn local<A, R, F>(f: F, r: Reader<A, R>) -> Reader<A, R>
where
A: 'static,
R: 'static,
F: FnOnce(R) -> R + 'static,
{
reader(move |env| run(r, f(env)))
}
#[inline]
pub fn provide<A, R>(r: Reader<A, R>, env: R) -> Reader<A, ()>
where
A: 'static,
R: 'static,
{
reader(move |_: ()| run(r, env))
}
#[inline]
pub fn asks<A, R, S, F>(f: F, r: Reader<A, S>) -> Reader<A, R>
where
A: 'static,
R: 'static,
S: 'static,
F: FnOnce(R) -> S + 'static,
{
reader(move |env: R| run(r, f(env)))
}
#[inline]
pub fn map2<A, B, C, R, F>(ra: Reader<A, R>, rb: Reader<B, R>, f: F) -> Reader<C, R>
where
A: 'static,
B: 'static,
C: 'static,
R: Clone + 'static,
F: FnOnce(A, B) -> C + 'static,
{
flat_map(ra, move |a| map(rb, move |b| f(a, b)))
}
#[inline]
pub fn zip<A, B, R>(ra: Reader<A, R>, rb: Reader<B, R>) -> Reader<(A, B), R>
where
A: 'static,
B: 'static,
R: Clone + 'static,
{
map2(ra, rb, |a, b| (a, b))
}
#[inline]
pub fn zip_left<A, B, R>(ra: Reader<A, R>, rb: Reader<B, R>) -> Reader<A, R>
where
A: 'static,
B: 'static,
R: Clone + 'static,
{
map2(ra, rb, |a, _| a)
}
#[inline]
pub fn zip_right<A, B, R>(ra: Reader<A, R>, rb: Reader<B, R>) -> Reader<B, R>
where
A: 'static,
B: 'static,
R: Clone + 'static,
{
map2(ra, rb, |_, b| b)
}
#[cfg(test)]
mod tests {
use super::*;
use rstest::rstest;
mod constructors {
use super::*;
#[test]
fn reader_creates_from_function() {
let r = reader(|env: i32| env * 2);
assert_eq!(run(r, 21), 42);
}
#[test]
fn ask_returns_environment() {
let r = ask::<String>();
assert_eq!(run(r, "hello".to_string()), "hello");
}
#[test]
fn pure_ignores_environment() {
let r = pure::<_, i32>(42);
assert_eq!(run(r, 999), 42);
}
#[test]
fn succeed_is_pure() {
let r = succeed::<_, i32>(42);
assert_eq!(run(r, 999), 42);
}
}
mod functor_operations {
use super::*;
#[test]
fn map_transforms_result() {
let r = map(ask::<i32>(), |n| n * 2);
assert_eq!(run(r, 21), 42);
}
#[test]
fn as_replaces_result() {
let r = as_(ask::<i32>(), "replaced");
assert_eq!(run(r, 21), "replaced");
}
#[test]
fn void_discards_result() {
let r = void(ask::<i32>());
assert_eq!(run(r, 21), ());
}
#[rstest]
#[case::identity(5, |x| x, 5)]
#[case::double(3, |x| x * 2, 6)]
#[case::negate(7, |x: i32| -x, -7)]
fn map_applies_function(#[case] env: i32, #[case] f: fn(i32) -> i32, #[case] expected: i32) {
let r = map(ask::<i32>(), f);
assert_eq!(run(r, env), expected);
}
}
mod monad_operations {
use super::*;
#[test]
fn flat_map_sequences_readers() {
let r = flat_map(ask::<i32>(), |n| pure(n * 2));
assert_eq!(run(r, 21), 42);
}
#[test]
fn flat_map_shares_environment() {
let r = flat_map(ask::<i32>(), |n| map(ask::<i32>(), move |m| n + m));
assert_eq!(run(r, 10), 20); }
#[test]
fn flatten_unwraps_nested_reader() {
let r = pure::<_, i32>(pure::<_, i32>(42));
let flattened = flatten(r);
assert_eq!(run(flattened, 0), 42);
}
}
mod environment_operations {
use super::*;
#[test]
fn local_modifies_environment() {
let r = local(|n: i32| n * 2, ask::<i32>());
assert_eq!(run(r, 21), 42);
}
#[test]
fn local_only_affects_inner_reader() {
let inner = local(|n: i32| n * 2, ask::<i32>());
let outer = flat_map(ask::<i32>(), move |n| map(inner, move |m| (n, m)));
assert_eq!(run(outer, 10), (10, 20));
}
#[test]
fn provide_fixes_environment() {
let r = provide(ask::<i32>(), 42);
assert_eq!(run(r, ()), 42);
}
#[test]
fn asks_projects_environment() {
#[derive(Clone)]
struct Config {
value: i32,
}
let r = asks(|c: Config| c.value, ask::<i32>());
assert_eq!(run(r, Config { value: 42 }), 42);
}
}
mod applicative_operations {
use super::*;
#[test]
fn map2_combines_readers() {
let ra = ask::<i32>();
let rb = map(ask::<i32>(), |n| n * 2);
let combined = map2(ra, rb, |a, b| a + b);
assert_eq!(run(combined, 10), 30);
}
#[test]
fn zip_creates_pair() {
let ra = ask::<i32>();
let rb = map(ask::<i32>(), |n| format!("{n}"));
let zipped = zip(ra, rb);
assert_eq!(run(zipped, 42), (42, "42".to_string()));
}
#[test]
fn zip_left_keeps_first() {
let ra = pure::<_, i32>(1);
let rb = pure::<_, i32>(2);
assert_eq!(run(zip_left(ra, rb), 0), 1);
}
#[test]
fn zip_right_keeps_second() {
let ra = pure::<_, i32>(1);
let rb = pure::<_, i32>(2);
assert_eq!(run(zip_right(ra, rb), 0), 2);
}
}
mod laws {
use super::*;
#[test]
fn functor_identity() {
let r = ask::<i32>();
let mapped = map(r, |x| x);
assert_eq!(run(mapped, 42), 42);
}
#[test]
fn functor_composition() {
let f = |x: i32| x + 1;
let g = |x: i32| x * 2;
let r1 = ask::<i32>();
let left = map(map(r1, g), f);
let r2 = ask::<i32>();
let right = map(r2, move |x| f(g(x)));
assert_eq!(run(left, 5), run(right, 5));
}
#[test]
fn monad_left_identity() {
let a = 5;
let f = |x: i32| pure::<_, i32>(x * 2);
let left = flat_map(pure(a), f);
let right = f(a);
assert_eq!(run(left, 0), run(right, 0));
}
#[test]
fn monad_right_identity() {
let r = ask::<i32>();
let result = flat_map(r, |x| pure(x));
assert_eq!(run(result, 42), 42);
}
#[test]
fn monad_associativity() {
let f = |x: i32| pure::<_, i32>(x + 1);
let g = |x: i32| pure::<_, i32>(x * 2);
let r1 = ask::<i32>();
let left = flat_map(flat_map(r1, f), g);
let r2 = ask::<i32>();
let right = flat_map(r2, move |a| flat_map(f(a), g));
assert_eq!(run(left, 5), run(right, 5));
}
}
mod reader_fn_reusable {
use super::*;
#[test]
fn reader_fn_can_be_run_multiple_times() {
let r = reader_fn(|env: &i32| *env * 2);
assert_eq!(run_fn(&r, &21), 42);
assert_eq!(run_fn(&r, &10), 20);
assert_eq!(run_fn(&r, &5), 10);
}
}
}