Skip to main content

karpal_effect/
reader_t.rs

1use core::marker::PhantomData;
2
3use karpal_core::hkt::HKT;
4
5use crate::classes::{ApplicativeSt, ChainSt, FunctorSt};
6use crate::trans::MonadTrans;
7
8#[cfg(all(not(feature = "std"), feature = "alloc"))]
9use alloc::rc::Rc;
10#[cfg(feature = "std")]
11use std::rc::Rc;
12
13/// ReaderT monad transformer: adds environment-passing to an inner monad.
14///
15/// `ReaderTF<E, M>::Of<A> = Box<dyn Fn(E) -> M::Of<A>>`
16///
17/// When the inner monad is `IdentityF`, this is equivalent to `ReaderF<E>`.
18pub struct ReaderTF<E, M>(PhantomData<(E, M)>);
19
20impl<E: 'static, M: HKT + 'static> HKT for ReaderTF<E, M> {
21    type Of<A> = Box<dyn Fn(E) -> M::Of<A>>;
22}
23
24impl<E: 'static, M: FunctorSt + 'static> MonadTrans<M> for ReaderTF<E, M> {
25    fn lift<A: 'static>(ma: M::Of<A>) -> Box<dyn Fn(E) -> M::Of<A>>
26    where
27        M::Of<A>: Clone,
28    {
29        Box::new(move |_| ma.clone())
30    }
31}
32
33/// ReaderT `pure`: wrap a value, ignoring the environment.
34pub fn reader_t_pure<E: 'static, M: ApplicativeSt + 'static, A: Clone + 'static>(
35    a: A,
36) -> Box<dyn Fn(E) -> M::Of<A>> {
37    Box::new(move |_| M::pure_st(a.clone()))
38}
39
40/// ReaderT `fmap`: apply a function to the result.
41pub fn reader_t_fmap<E: 'static, M: FunctorSt + 'static, A: 'static, B: 'static>(
42    fa: Box<dyn Fn(E) -> M::Of<A>>,
43    f: impl Fn(A) -> B + 'static,
44) -> Box<dyn Fn(E) -> M::Of<B>> {
45    let f_rc = Rc::new(f);
46    Box::new(move |e| {
47        let ma = fa(e);
48        let f_inner = f_rc.clone();
49        M::fmap_st(ma, move |a| f_inner(a))
50    })
51}
52
53/// ReaderT `chain`: sequence environment-passing computations.
54///
55/// The environment is shared (not threaded) between computations.
56pub fn reader_t_chain<E: Clone + 'static, M: ChainSt + 'static, A: 'static, B: 'static>(
57    fa: Box<dyn Fn(E) -> M::Of<A>>,
58    f: impl Fn(A) -> Box<dyn Fn(E) -> M::Of<B>> + 'static,
59) -> Box<dyn Fn(E) -> M::Of<B>> {
60    let f_rc = Rc::new(f);
61    Box::new(move |e: E| {
62        let ma = fa(e.clone());
63        let e2 = e;
64        let f_inner = f_rc.clone();
65        M::chain_st(ma, move |a| {
66            let reader_b = f_inner(a);
67            reader_b(e2.clone())
68        })
69    })
70}
71
72/// ReaderT `ask`: get the current environment.
73pub fn reader_t_ask<E: Clone + 'static, M: ApplicativeSt + 'static>() -> Box<dyn Fn(E) -> M::Of<E>>
74{
75    Box::new(|e| M::pure_st(e))
76}
77
78/// ReaderT `local`: modify the environment for a sub-computation.
79pub fn reader_t_local<E: 'static, M: HKT + 'static, A: 'static>(
80    f: impl Fn(E) -> E + 'static,
81    reader: Box<dyn Fn(E) -> M::Of<A>>,
82) -> Box<dyn Fn(E) -> M::Of<A>> {
83    Box::new(move |e| reader(f(e)))
84}
85
86/// ReaderT `reader`: create a computation from a function on the environment.
87pub fn reader_t_reader<E: 'static, M: ApplicativeSt + 'static, A: 'static>(
88    f: impl Fn(E) -> A + 'static,
89) -> Box<dyn Fn(E) -> M::Of<A>> {
90    Box::new(move |e| M::pure_st(f(e)))
91}
92
93/// ReaderT `run`: run the computation with an environment.
94pub fn reader_t_run<E, M: HKT, A>(reader: &dyn Fn(E) -> M::Of<A>, env: E) -> M::Of<A> {
95    reader(env)
96}
97
98// --- FunctorSt / ChainSt for ReaderTF ---
99// Note: ApplicativeSt is not implemented for ReaderTF because pure_st
100// cannot produce a Box<dyn Fn(E) -> M::Of<A>> from a single A without Clone.
101// Use the standalone reader_t_pure function instead (which requires A: Clone).
102
103impl<E: 'static, M: FunctorSt + 'static> FunctorSt for ReaderTF<E, M> {
104    fn fmap_st<A: 'static, B: 'static>(
105        fa: Box<dyn Fn(E) -> M::Of<A>>,
106        f: impl Fn(A) -> B + 'static,
107    ) -> Box<dyn Fn(E) -> M::Of<B>> {
108        reader_t_fmap::<E, M, A, B>(fa, f)
109    }
110}
111
112impl<E: Clone + 'static, M: ChainSt + 'static> ChainSt for ReaderTF<E, M> {
113    fn chain_st<A: 'static, B: 'static>(
114        fa: Box<dyn Fn(E) -> M::Of<A>>,
115        f: impl Fn(A) -> Box<dyn Fn(E) -> M::Of<B>> + 'static,
116    ) -> Box<dyn Fn(E) -> M::Of<B>> {
117        reader_t_chain::<E, M, A, B>(fa, f)
118    }
119}
120
121#[cfg(test)]
122mod tests {
123    use super::*;
124    use karpal_core::hkt::OptionF;
125
126    #[test]
127    fn reader_t_pure_test() {
128        let r = reader_t_pure::<i32, OptionF, _>(42);
129        assert_eq!(r(0), Some(42));
130        assert_eq!(r(999), Some(42));
131    }
132
133    #[test]
134    fn reader_t_ask_test() {
135        let r = reader_t_ask::<i32, OptionF>();
136        assert_eq!(r(42), Some(42));
137    }
138
139    #[test]
140    fn reader_t_fmap_test() {
141        let r = reader_t_ask::<i32, OptionF>();
142        let mapped = reader_t_fmap::<i32, OptionF, _, _>(r, |x| x * 2);
143        assert_eq!(mapped(5), Some(10));
144    }
145
146    #[test]
147    fn reader_t_chain_test() {
148        let r = reader_t_ask::<i32, OptionF>();
149        let chained =
150            reader_t_chain::<i32, OptionF, _, _>(r, |x| reader_t_pure::<i32, OptionF, _>(x + 10));
151        assert_eq!(chained(5), Some(15));
152    }
153
154    #[test]
155    fn reader_t_chain_shares_env() {
156        let r = reader_t_chain::<i32, OptionF, _, _>(reader_t_ask::<i32, OptionF>(), |x| {
157            let x_captured = x;
158            reader_t_fmap::<i32, OptionF, _, _>(reader_t_ask::<i32, OptionF>(), move |e| {
159                e + x_captured
160            })
161        });
162        assert_eq!(r(10), Some(20));
163    }
164
165    #[test]
166    fn reader_t_local_test() {
167        let r = reader_t_ask::<i32, OptionF>();
168        let localized = reader_t_local::<i32, OptionF, i32>(|e| e + 100, r);
169        assert_eq!(localized(5), Some(105));
170    }
171
172    #[test]
173    fn reader_t_reader_test() {
174        let r = reader_t_reader::<String, OptionF, _>(|s: String| s.len());
175        assert_eq!(r("hello".to_string()), Some(5));
176    }
177
178    #[test]
179    fn reader_t_lift_test() {
180        let lifted = ReaderTF::<i32, OptionF>::lift(Some(42));
181        assert_eq!(lifted(999), Some(42));
182    }
183
184    #[test]
185    fn reader_t_lift_none() {
186        let lifted = ReaderTF::<i32, OptionF>::lift(None::<i32>);
187        assert_eq!(lifted(999), None);
188    }
189
190    #[test]
191    fn reader_t_run_test() {
192        let r = reader_t_ask::<i32, OptionF>();
193        assert_eq!(reader_t_run::<i32, OptionF, i32>(&*r, 42), Some(42));
194    }
195
196    #[test]
197    fn reader_t_functor_st_trait() {
198        let r = reader_t_pure::<i32, OptionF, _>(5);
199        let mapped = ReaderTF::<i32, OptionF>::fmap_st(r, |x| x + 1);
200        assert_eq!(mapped(0), Some(6));
201    }
202
203    #[test]
204    fn reader_t_chain_st_trait() {
205        let r = reader_t_pure::<i32, OptionF, _>(5);
206        let chained =
207            ReaderTF::<i32, OptionF>::chain_st(r, |x| reader_t_pure::<i32, OptionF, _>(x + 10));
208        assert_eq!(chained(0), Some(15));
209    }
210}
211
212#[cfg(test)]
213mod law_tests {
214    use super::*;
215    use karpal_core::hkt::OptionF;
216    use proptest::prelude::*;
217
218    proptest! {
219        #[test]
220        fn reader_t_monad_left_identity(a in -100i32..100, e in -100i32..100) {
221            let f = |x: i32| -> Box<dyn Fn(i32) -> Option<i32>> {
222                reader_t_pure::<i32, OptionF, _>(x + 1)
223            };
224            let left = reader_t_chain::<i32, OptionF, _, _>(
225                reader_t_pure::<i32, OptionF, _>(a),
226                f,
227            );
228            let right = f(a);
229            prop_assert_eq!(left(e), right(e));
230        }
231
232        #[test]
233        fn reader_t_monad_right_identity(a in -100i32..100, e in -100i32..100) {
234            let m = reader_t_pure::<i32, OptionF, _>(a);
235            let left = reader_t_chain::<i32, OptionF, _, _>(
236                reader_t_pure::<i32, OptionF, _>(a),
237                |x| reader_t_pure::<i32, OptionF, _>(x),
238            );
239            prop_assert_eq!(left(e), m(e));
240        }
241
242        #[test]
243        fn reader_t_functor_identity(a in -100i32..100, e in -100i32..100) {
244            let m = reader_t_pure::<i32, OptionF, _>(a);
245            let mapped = reader_t_fmap::<i32, OptionF, _, _>(
246                reader_t_pure::<i32, OptionF, _>(a),
247                |x| x,
248            );
249            prop_assert_eq!(mapped(e), m(e));
250        }
251
252        #[test]
253        fn reader_t_lift_pure(a in any::<i32>(), e in any::<i32>()) {
254            let lift_pure = ReaderTF::<i32, OptionF>::lift(Some(a));
255            let pure_a = reader_t_pure::<i32, OptionF, _>(a);
256            prop_assert_eq!(lift_pure(e), pure_a(e));
257        }
258    }
259}