use alloc::boxed::Box;
use core::marker::PhantomData;
use crate::nexus::effect::{Eff, EffectMarker};
use crate::nexus::row::{READER_BIT, Row};
#[derive(Copy, Clone, Debug)]
pub struct ReaderEffect<E> {
_marker: PhantomData<E>,
}
impl<E> EffectMarker for ReaderEffect<E> {
const BIT: u128 = READER_BIT;
const NAME: &'static str = "Reader";
}
pub type ReaderRow = Row<READER_BIT>;
pub enum ReaderOp<E, A> {
Ask,
Asks(Box<dyn FnOnce(&E) -> A>),
Local(Box<dyn FnOnce(E) -> E>),
}
pub fn reader_ask<E: Clone + 'static>() -> Eff<ReaderRow, E> {
Eff::lazy(|| crate::cold_panic!("reader_ask requires Reader handler"))
}
pub fn reader_asks<E: 'static, A: 'static, F: FnOnce(&E) -> A + 'static>(
_f: F,
) -> Eff<ReaderRow, A> {
Eff::lazy(|| crate::cold_panic!("reader_asks requires Reader handler"))
}
#[must_use = "computations do nothing unless run"]
#[repr(u8)]
pub enum ReaderComputation<E, A> {
Pure(A),
Boxed(Box<dyn FnOnce(&E) -> A>),
}
impl<E: 'static, A: 'static> ReaderComputation<E, A> {
#[inline(always)]
pub fn new<F: FnOnce(&E) -> A + 'static>(f: F) -> Self {
ReaderComputation::Boxed(Box::new(f))
}
#[inline(always)]
pub fn run(self, env: &E) -> A {
match self {
ReaderComputation::Pure(a) => a,
ReaderComputation::Boxed(f) => f(env),
}
}
#[inline(always)]
pub fn pure(value: A) -> Self {
ReaderComputation::Pure(value)
}
#[inline(always)]
pub fn ask() -> ReaderComputation<E, E>
where
E: Clone,
{
ReaderComputation::new(|e: &E| e.clone())
}
#[inline(always)]
pub fn asks<F: FnOnce(&E) -> A + 'static>(f: F) -> Self {
ReaderComputation::new(f)
}
#[inline(always)]
pub fn map<B: 'static, F: FnOnce(A) -> B + 'static>(self, f: F) -> ReaderComputation<E, B> {
match self {
ReaderComputation::Pure(a) => ReaderComputation::Pure(f(a)),
ReaderComputation::Boxed(run_fn) => ReaderComputation::new(move |e| f(run_fn(e))),
}
}
#[inline(always)]
pub fn and_then<B: 'static, F: FnOnce(A) -> ReaderComputation<E, B> + 'static>(
self,
f: F,
) -> ReaderComputation<E, B> {
ReaderComputation::new(move |e| {
let a = self.run(e);
f(a).run(e)
})
}
#[inline(always)]
pub fn local<F: FnOnce(&E) -> E + 'static>(self, modify: F) -> ReaderComputation<E, A> {
ReaderComputation::new(move |e| {
let new_env = modify(e);
self.run(&new_env)
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[derive(Clone)]
struct TestEnv {
value: i32,
multiplier: i32,
}
#[test]
fn test_reader_pure() {
let comp = ReaderComputation::<TestEnv, i32>::pure(42);
let result = comp.run(&TestEnv {
value: 0,
multiplier: 1,
});
assert_eq!(result, 42);
}
#[test]
fn test_reader_ask() {
let comp = ReaderComputation::<TestEnv, TestEnv>::ask();
let env = TestEnv {
value: 42,
multiplier: 2,
};
let result = comp.run(&env);
assert_eq!(result.value, 42);
}
#[test]
fn test_reader_asks() {
let comp = ReaderComputation::<TestEnv, i32>::asks(|e| e.value);
let result = comp.run(&TestEnv {
value: 42,
multiplier: 2,
});
assert_eq!(result, 42);
}
#[test]
fn test_reader_map() {
let comp = ReaderComputation::<TestEnv, i32>::asks(|e| e.value).map(|x| x * 2);
let result = comp.run(&TestEnv {
value: 21,
multiplier: 2,
});
assert_eq!(result, 42);
}
#[test]
fn test_reader_and_then() {
let comp = ReaderComputation::<TestEnv, i32>::asks(|e| e.value).and_then(|v| {
ReaderComputation::<TestEnv, i32>::asks(move |e: &TestEnv| v * e.multiplier)
});
let result = comp.run(&TestEnv {
value: 21,
multiplier: 2,
});
assert_eq!(result, 42);
}
#[test]
fn test_reader_local() {
let comp = ReaderComputation::<TestEnv, i32>::asks(|e| e.value).local(|e| TestEnv {
value: e.value + 10,
multiplier: e.multiplier,
});
let result = comp.run(&TestEnv {
value: 32,
multiplier: 1,
});
assert_eq!(result, 42);
}
}