use core::marker::PhantomData;
use karpal_core::hkt::HKT;
use crate::classes::{ApplicativeSt, ChainSt, FunctorSt};
use crate::trans::MonadTrans;
#[cfg(all(not(feature = "std"), feature = "alloc"))]
use alloc::rc::Rc;
#[cfg(feature = "std")]
use std::rc::Rc;
pub struct ReaderTF<E, M>(PhantomData<(E, M)>);
impl<E: 'static, M: HKT + 'static> HKT for ReaderTF<E, M> {
type Of<A> = Box<dyn Fn(E) -> M::Of<A>>;
}
impl<E: 'static, M: FunctorSt + 'static> MonadTrans<M> for ReaderTF<E, M> {
fn lift<A: 'static>(ma: M::Of<A>) -> Box<dyn Fn(E) -> M::Of<A>>
where
M::Of<A>: Clone,
{
Box::new(move |_| ma.clone())
}
}
pub fn reader_t_pure<E: 'static, M: ApplicativeSt + 'static, A: Clone + 'static>(
a: A,
) -> Box<dyn Fn(E) -> M::Of<A>> {
Box::new(move |_| M::pure_st(a.clone()))
}
pub fn reader_t_fmap<E: 'static, M: FunctorSt + 'static, A: 'static, B: 'static>(
fa: Box<dyn Fn(E) -> M::Of<A>>,
f: impl Fn(A) -> B + 'static,
) -> Box<dyn Fn(E) -> M::Of<B>> {
let f_rc = Rc::new(f);
Box::new(move |e| {
let ma = fa(e);
let f_inner = f_rc.clone();
M::fmap_st(ma, move |a| f_inner(a))
})
}
pub fn reader_t_chain<E: Clone + 'static, M: ChainSt + 'static, A: 'static, B: 'static>(
fa: Box<dyn Fn(E) -> M::Of<A>>,
f: impl Fn(A) -> Box<dyn Fn(E) -> M::Of<B>> + 'static,
) -> Box<dyn Fn(E) -> M::Of<B>> {
let f_rc = Rc::new(f);
Box::new(move |e: E| {
let ma = fa(e.clone());
let e2 = e;
let f_inner = f_rc.clone();
M::chain_st(ma, move |a| {
let reader_b = f_inner(a);
reader_b(e2.clone())
})
})
}
pub fn reader_t_ask<E: Clone + 'static, M: ApplicativeSt + 'static>() -> Box<dyn Fn(E) -> M::Of<E>>
{
Box::new(|e| M::pure_st(e))
}
pub fn reader_t_local<E: 'static, M: HKT + 'static, A: 'static>(
f: impl Fn(E) -> E + 'static,
reader: Box<dyn Fn(E) -> M::Of<A>>,
) -> Box<dyn Fn(E) -> M::Of<A>> {
Box::new(move |e| reader(f(e)))
}
pub fn reader_t_reader<E: 'static, M: ApplicativeSt + 'static, A: 'static>(
f: impl Fn(E) -> A + 'static,
) -> Box<dyn Fn(E) -> M::Of<A>> {
Box::new(move |e| M::pure_st(f(e)))
}
pub fn reader_t_run<E, M: HKT, A>(reader: &dyn Fn(E) -> M::Of<A>, env: E) -> M::Of<A> {
reader(env)
}
impl<E: 'static, M: FunctorSt + 'static> FunctorSt for ReaderTF<E, M> {
fn fmap_st<A: 'static, B: 'static>(
fa: Box<dyn Fn(E) -> M::Of<A>>,
f: impl Fn(A) -> B + 'static,
) -> Box<dyn Fn(E) -> M::Of<B>> {
reader_t_fmap::<E, M, A, B>(fa, f)
}
}
impl<E: Clone + 'static, M: ChainSt + 'static> ChainSt for ReaderTF<E, M> {
fn chain_st<A: 'static, B: 'static>(
fa: Box<dyn Fn(E) -> M::Of<A>>,
f: impl Fn(A) -> Box<dyn Fn(E) -> M::Of<B>> + 'static,
) -> Box<dyn Fn(E) -> M::Of<B>> {
reader_t_chain::<E, M, A, B>(fa, f)
}
}
#[cfg(test)]
mod tests {
use super::*;
use karpal_core::hkt::OptionF;
#[test]
fn reader_t_pure_test() {
let r = reader_t_pure::<i32, OptionF, _>(42);
assert_eq!(r(0), Some(42));
assert_eq!(r(999), Some(42));
}
#[test]
fn reader_t_ask_test() {
let r = reader_t_ask::<i32, OptionF>();
assert_eq!(r(42), Some(42));
}
#[test]
fn reader_t_fmap_test() {
let r = reader_t_ask::<i32, OptionF>();
let mapped = reader_t_fmap::<i32, OptionF, _, _>(r, |x| x * 2);
assert_eq!(mapped(5), Some(10));
}
#[test]
fn reader_t_chain_test() {
let r = reader_t_ask::<i32, OptionF>();
let chained =
reader_t_chain::<i32, OptionF, _, _>(r, |x| reader_t_pure::<i32, OptionF, _>(x + 10));
assert_eq!(chained(5), Some(15));
}
#[test]
fn reader_t_chain_shares_env() {
let r = reader_t_chain::<i32, OptionF, _, _>(reader_t_ask::<i32, OptionF>(), |x| {
let x_captured = x;
reader_t_fmap::<i32, OptionF, _, _>(reader_t_ask::<i32, OptionF>(), move |e| {
e + x_captured
})
});
assert_eq!(r(10), Some(20));
}
#[test]
fn reader_t_local_test() {
let r = reader_t_ask::<i32, OptionF>();
let localized = reader_t_local::<i32, OptionF, i32>(|e| e + 100, r);
assert_eq!(localized(5), Some(105));
}
#[test]
fn reader_t_reader_test() {
let r = reader_t_reader::<String, OptionF, _>(|s: String| s.len());
assert_eq!(r("hello".to_string()), Some(5));
}
#[test]
fn reader_t_lift_test() {
let lifted = ReaderTF::<i32, OptionF>::lift(Some(42));
assert_eq!(lifted(999), Some(42));
}
#[test]
fn reader_t_lift_none() {
let lifted = ReaderTF::<i32, OptionF>::lift(None::<i32>);
assert_eq!(lifted(999), None);
}
#[test]
fn reader_t_run_test() {
let r = reader_t_ask::<i32, OptionF>();
assert_eq!(reader_t_run::<i32, OptionF, i32>(&*r, 42), Some(42));
}
#[test]
fn reader_t_functor_st_trait() {
let r = reader_t_pure::<i32, OptionF, _>(5);
let mapped = ReaderTF::<i32, OptionF>::fmap_st(r, |x| x + 1);
assert_eq!(mapped(0), Some(6));
}
#[test]
fn reader_t_chain_st_trait() {
let r = reader_t_pure::<i32, OptionF, _>(5);
let chained =
ReaderTF::<i32, OptionF>::chain_st(r, |x| reader_t_pure::<i32, OptionF, _>(x + 10));
assert_eq!(chained(0), Some(15));
}
}
#[cfg(test)]
mod law_tests {
use super::*;
use karpal_core::hkt::OptionF;
use proptest::prelude::*;
proptest! {
#[test]
fn reader_t_monad_left_identity(a in -100i32..100, e in -100i32..100) {
let f = |x: i32| -> Box<dyn Fn(i32) -> Option<i32>> {
reader_t_pure::<i32, OptionF, _>(x + 1)
};
let left = reader_t_chain::<i32, OptionF, _, _>(
reader_t_pure::<i32, OptionF, _>(a),
f,
);
let right = f(a);
prop_assert_eq!(left(e), right(e));
}
#[test]
fn reader_t_monad_right_identity(a in -100i32..100, e in -100i32..100) {
let m = reader_t_pure::<i32, OptionF, _>(a);
let left = reader_t_chain::<i32, OptionF, _, _>(
reader_t_pure::<i32, OptionF, _>(a),
|x| reader_t_pure::<i32, OptionF, _>(x),
);
prop_assert_eq!(left(e), m(e));
}
#[test]
fn reader_t_functor_identity(a in -100i32..100, e in -100i32..100) {
let m = reader_t_pure::<i32, OptionF, _>(a);
let mapped = reader_t_fmap::<i32, OptionF, _, _>(
reader_t_pure::<i32, OptionF, _>(a),
|x| x,
);
prop_assert_eq!(mapped(e), m(e));
}
#[test]
fn reader_t_lift_pure(a in any::<i32>(), e in any::<i32>()) {
let lift_pure = ReaderTF::<i32, OptionF>::lift(Some(a));
let pure_a = reader_t_pure::<i32, OptionF, _>(a);
prop_assert_eq!(lift_pure(e), pure_a(e));
}
}
}