Skip to main content

karpal_core/
compose.rs

1use core::marker::PhantomData;
2
3use crate::functor::Functor;
4use crate::hkt::HKT;
5
6/// Marker type for the composition of two type constructors: `(F . G)(A) = F(G(A))`.
7///
8/// Given `F: HKT` and `G: HKT`, `ComposeF<F, G>` is itself an HKT where
9/// `Of<A> = F::Of<G::Of<A>>`.
10pub struct ComposeF<F, G>(PhantomData<(F, G)>);
11
12impl<F: HKT, G: HKT> HKT for ComposeF<F, G> {
13    type Of<T> = F::Of<G::Of<T>>;
14}
15
16/// Functors compose: if F and G are both Functors, so is F . G.
17impl<F: Functor, G: Functor> Functor for ComposeF<F, G> {
18    fn fmap<A, B>(fga: F::Of<G::Of<A>>, f: impl Fn(A) -> B) -> F::Of<G::Of<B>> {
19        F::fmap(fga, |ga| G::fmap(ga, &f))
20    }
21}
22
23// Note: Apply/Applicative for ComposeF requires distributing function application
24// through two layers, which doesn't compose cleanly with Karpal's `fn(A) -> B`
25// representation (function pointers rather than closures). Functor composition is
26// the primary use case for adjunction-derived monads/comonads.
27
28#[cfg(test)]
29mod tests {
30    use super::*;
31    #[cfg(any(feature = "std", feature = "alloc"))]
32    use crate::hkt::VecF;
33    use crate::hkt::{IdentityF, OptionF};
34
35    #[test]
36    fn compose_option_option_fmap() {
37        // ComposeF<OptionF, OptionF>::Of<i32> = Option<Option<i32>>
38        let val: Option<Option<i32>> = Some(Some(42));
39        let result = ComposeF::<OptionF, OptionF>::fmap(val, |x| x + 1);
40        assert_eq!(result, Some(Some(43)));
41    }
42
43    #[test]
44    fn compose_option_option_fmap_outer_none() {
45        let val: Option<Option<i32>> = None;
46        let result = ComposeF::<OptionF, OptionF>::fmap(val, |x| x + 1);
47        assert_eq!(result, None);
48    }
49
50    #[test]
51    fn compose_option_option_fmap_inner_none() {
52        let val: Option<Option<i32>> = Some(None);
53        let result = ComposeF::<OptionF, OptionF>::fmap(val, |x| x + 1);
54        assert_eq!(result, Some(None));
55    }
56
57    #[test]
58    fn compose_identity_option() {
59        // ComposeF<IdentityF, OptionF>::Of<i32> = Option<i32>
60        let val: Option<i32> = Some(10);
61        let result = ComposeF::<IdentityF, OptionF>::fmap(val, |x| x * 2);
62        assert_eq!(result, Some(20));
63    }
64
65    #[test]
66    fn compose_option_identity() {
67        // ComposeF<OptionF, IdentityF>::Of<i32> = Option<i32>
68        let val: Option<i32> = Some(10);
69        let result = ComposeF::<OptionF, IdentityF>::fmap(val, |x| x * 2);
70        assert_eq!(result, Some(20));
71    }
72
73    #[cfg(any(feature = "std", feature = "alloc"))]
74    #[test]
75    fn compose_vec_option_fmap() {
76        // ComposeF<VecF, OptionF>::Of<i32> = Vec<Option<i32>>
77        let val: Vec<Option<i32>> = vec![Some(1), None, Some(3)];
78        let result = ComposeF::<VecF, OptionF>::fmap(val, |x| x * 10);
79        assert_eq!(result, vec![Some(10), None, Some(30)]);
80    }
81
82    #[cfg(any(feature = "std", feature = "alloc"))]
83    #[test]
84    fn compose_option_vec_fmap() {
85        // ComposeF<OptionF, VecF>::Of<i32> = Option<Vec<i32>>
86        let val: Option<Vec<i32>> = Some(vec![1, 2, 3]);
87        let result = ComposeF::<OptionF, VecF>::fmap(val, |x| x + 1);
88        assert_eq!(result, Some(vec![2, 3, 4]));
89    }
90}
91
92#[cfg(test)]
93mod law_tests {
94    use super::*;
95    use crate::hkt::OptionF;
96    use proptest::prelude::*;
97
98    proptest! {
99        // Identity law: fmap(id, fga) == fga
100        #[test]
101        fn compose_option_option_identity(x in any::<Option<Option<i32>>>()) {
102            let result = ComposeF::<OptionF, OptionF>::fmap(x.clone(), |a| a);
103            prop_assert_eq!(result, x);
104        }
105
106        // Composition law: fmap(g . f, fga) == fmap(g, fmap(f, fga))
107        #[test]
108        fn compose_option_option_composition(x in any::<Option<Option<i32>>>()) {
109            let f = |a: i32| a.wrapping_add(1);
110            let g = |a: i32| a.wrapping_mul(2);
111            let left = ComposeF::<OptionF, OptionF>::fmap(x.clone(), |a| g(f(a)));
112            let right = ComposeF::<OptionF, OptionF>::fmap(
113                ComposeF::<OptionF, OptionF>::fmap(x, f),
114                g,
115            );
116            prop_assert_eq!(left, right);
117        }
118    }
119}