Skip to main content

karpal_effect/
classes.rs

1use karpal_core::hkt::{HKT, IdentityF, OptionF, ResultF};
2
3/// Functor with `'static` bounds on type parameters.
4///
5/// This mirrors [`karpal_core::Functor`] but adds `'static` bounds required by
6/// types that use `Box<dyn Fn>` internally (monad transformers, `ReaderF`, etc.).
7/// Base types that implement `Functor` can typically implement this trivially
8/// because their type parameters often already satisfy `'static` (e.g., for owned types).
9pub trait FunctorSt: HKT {
10    fn fmap_st<A: 'static, B: 'static>(
11        fa: Self::Of<A>,
12        f: impl Fn(A) -> B + 'static,
13    ) -> Self::Of<B>;
14}
15
16/// Applicative with `'static` bounds on type parameters.
17///
18/// This mirrors [`karpal_core::Applicative`] but adds `'static` bounds required by
19/// types that use `Box<dyn Fn>` internally (monad transformers, `ReaderTF`, `StateTF`, etc.).
20/// Clone-requiring cases are handled by standalone helper functions (such as `*_t_pure`),
21/// so `pure_st` itself only requires `A: 'static`.
22pub trait ApplicativeSt: FunctorSt {
23    fn pure_st<A: 'static>(a: A) -> Self::Of<A>;
24}
25
26/// Chain (monadic bind) with `'static` bounds on type parameters.
27pub trait ChainSt: FunctorSt {
28    fn chain_st<A: 'static, B: 'static>(
29        fa: Self::Of<A>,
30        f: impl Fn(A) -> Self::Of<B> + 'static,
31    ) -> Self::Of<B>;
32}
33
34// --- Base type implementations ---
35
36impl FunctorSt for OptionF {
37    fn fmap_st<A: 'static, B: 'static>(fa: Option<A>, f: impl Fn(A) -> B + 'static) -> Option<B> {
38        fa.map(f)
39    }
40}
41
42impl ApplicativeSt for OptionF {
43    fn pure_st<A: 'static>(a: A) -> Option<A> {
44        Some(a)
45    }
46}
47
48impl ChainSt for OptionF {
49    fn chain_st<A: 'static, B: 'static>(
50        fa: Option<A>,
51        f: impl Fn(A) -> Option<B> + 'static,
52    ) -> Option<B> {
53        fa.and_then(f)
54    }
55}
56
57impl<E: 'static> FunctorSt for ResultF<E> {
58    fn fmap_st<A: 'static, B: 'static>(
59        fa: Result<A, E>,
60        f: impl Fn(A) -> B + 'static,
61    ) -> Result<B, E> {
62        fa.map(f)
63    }
64}
65
66impl<E: 'static> ApplicativeSt for ResultF<E> {
67    fn pure_st<A: 'static>(a: A) -> Result<A, E> {
68        Ok(a)
69    }
70}
71
72impl<E: 'static> ChainSt for ResultF<E> {
73    fn chain_st<A: 'static, B: 'static>(
74        fa: Result<A, E>,
75        f: impl Fn(A) -> Result<B, E> + 'static,
76    ) -> Result<B, E> {
77        fa.and_then(f)
78    }
79}
80
81impl FunctorSt for IdentityF {
82    fn fmap_st<A: 'static, B: 'static>(fa: A, f: impl Fn(A) -> B + 'static) -> B {
83        f(fa)
84    }
85}
86
87impl ApplicativeSt for IdentityF {
88    fn pure_st<A: 'static>(a: A) -> A {
89        a
90    }
91}
92
93impl ChainSt for IdentityF {
94    fn chain_st<A: 'static, B: 'static>(fa: A, f: impl Fn(A) -> B + 'static) -> B {
95        f(fa)
96    }
97}
98
99#[cfg(any(feature = "std", feature = "alloc"))]
100impl FunctorSt for karpal_core::hkt::VecF {
101    fn fmap_st<A: 'static, B: 'static>(fa: Vec<A>, f: impl Fn(A) -> B + 'static) -> Vec<B> {
102        fa.into_iter().map(f).collect()
103    }
104}
105
106#[cfg(any(feature = "std", feature = "alloc"))]
107impl ApplicativeSt for karpal_core::hkt::VecF {
108    fn pure_st<A: 'static>(a: A) -> Vec<A> {
109        vec![a]
110    }
111}
112
113#[cfg(any(feature = "std", feature = "alloc"))]
114impl ChainSt for karpal_core::hkt::VecF {
115    fn chain_st<A: 'static, B: 'static>(fa: Vec<A>, f: impl Fn(A) -> Vec<B> + 'static) -> Vec<B> {
116        fa.into_iter().flat_map(f).collect()
117    }
118}
119
120#[cfg(test)]
121mod tests {
122    use super::*;
123
124    #[test]
125    fn option_functor_st() {
126        assert_eq!(OptionF::fmap_st(Some(3), |x| x + 1), Some(4));
127        assert_eq!(OptionF::fmap_st(None::<i32>, |x| x + 1), None);
128    }
129
130    #[test]
131    fn option_applicative_st() {
132        assert_eq!(OptionF::pure_st(42), Some(42));
133    }
134
135    #[test]
136    fn option_chain_st() {
137        assert_eq!(OptionF::chain_st(Some(3), |x| Some(x * 2)), Some(6));
138        assert_eq!(OptionF::chain_st(None::<i32>, |x| Some(x * 2)), None);
139    }
140
141    #[test]
142    fn result_functor_st() {
143        assert_eq!(ResultF::<&str>::fmap_st(Ok(3), |x| x + 1), Ok(4));
144    }
145
146    #[test]
147    fn identity_chain_st() {
148        assert_eq!(IdentityF::chain_st(5, |x| x + 1), 6);
149    }
150}
151
152#[cfg(test)]
153mod law_tests {
154    use super::*;
155    use proptest::prelude::*;
156
157    proptest! {
158        // Functor identity: fmap(id, fa) == fa
159        #[test]
160        fn option_functor_identity(x in any::<Option<i32>>()) {
161            prop_assert_eq!(OptionF::fmap_st(x.clone(), |a| a), x);
162        }
163
164        // Functor composition: fmap(g . f, fa) == fmap(g, fmap(f, fa))
165        #[test]
166        fn option_functor_composition(x in any::<Option<i16>>()) {
167            let f = |a: i16| a.wrapping_add(1);
168            let g = |a: i16| a.wrapping_mul(2);
169            let left = OptionF::fmap_st(x.clone(), move |a| g(f(a)));
170            let right = OptionF::fmap_st(OptionF::fmap_st(x, f), g);
171            prop_assert_eq!(left, right);
172        }
173
174        // Chain associativity
175        #[test]
176        fn option_chain_associativity(x in any::<Option<i16>>()) {
177            let f = |a: i16| Some(a.wrapping_add(1));
178            let g = |a: i16| Some(a.wrapping_mul(2));
179            let left = OptionF::chain_st(OptionF::chain_st(x.clone(), f), g);
180            let right = OptionF::chain_st(x, move |a| OptionF::chain_st(f(a), g));
181            prop_assert_eq!(left, right);
182        }
183
184        // Monad left identity: chain(pure(a), f) == f(a)
185        #[test]
186        fn option_monad_left_identity(a in any::<i16>()) {
187            let f = |x: i16| Some(x.wrapping_add(1));
188            let left = OptionF::chain_st(OptionF::pure_st(a), f);
189            let right = f(a);
190            prop_assert_eq!(left, right);
191        }
192
193        // Monad right identity: chain(m, pure) == m
194        #[test]
195        fn option_monad_right_identity(x in any::<Option<i32>>()) {
196            let left = OptionF::chain_st(x.clone(), |a| OptionF::pure_st(a));
197            prop_assert_eq!(left, x);
198        }
199    }
200}