Skip to main content

karpal_effect/
classes.rs

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