Skip to main content

karpal_core/
compose.rs

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