Skip to main content

karpal_core/
functor.rs

1use crate::hkt::{EnvF, HKT, IdentityF, OptionF, ResultF};
2#[cfg(any(feature = "std", feature = "alloc"))]
3use crate::hkt::{NonEmptyVec, NonEmptyVecF};
4
5/// Covariant functor: lifts a function `A -> B` into `F<A> -> F<B>`.
6pub trait Functor: HKT {
7    fn fmap<A, B>(fa: Self::Of<A>, f: impl Fn(A) -> B) -> Self::Of<B>;
8}
9
10impl Functor for OptionF {
11    fn fmap<A, B>(fa: Option<A>, f: impl Fn(A) -> B) -> Option<B> {
12        fa.map(f)
13    }
14}
15
16impl<E> Functor for ResultF<E> {
17    fn fmap<A, B>(fa: Result<A, E>, f: impl Fn(A) -> B) -> Result<B, E> {
18        fa.map(f)
19    }
20}
21
22#[cfg(any(feature = "std", feature = "alloc"))]
23impl Functor for crate::hkt::VecF {
24    fn fmap<A, B>(fa: Vec<A>, f: impl Fn(A) -> B) -> Vec<B> {
25        fa.into_iter().map(f).collect()
26    }
27}
28
29impl Functor for IdentityF {
30    fn fmap<A, B>(fa: A, f: impl Fn(A) -> B) -> B {
31        f(fa)
32    }
33}
34
35#[cfg(any(feature = "std", feature = "alloc"))]
36impl Functor for NonEmptyVecF {
37    fn fmap<A, B>(fa: NonEmptyVec<A>, f: impl Fn(A) -> B) -> NonEmptyVec<B> {
38        NonEmptyVec::new(f(fa.head), fa.tail.into_iter().map(&f).collect())
39    }
40}
41
42impl<E> Functor for EnvF<E> {
43    fn fmap<A, B>(fa: (E, A), f: impl Fn(A) -> B) -> (E, B) {
44        (fa.0, f(fa.1))
45    }
46}
47
48// Note: StoreF and TracedF cannot implement the generic Functor trait because
49// Box<dyn Fn> requires 'static bounds that the trait signature doesn't allow.
50// They get their own fmap via the Extend/Comonad implementation.
51
52#[cfg(test)]
53mod tests {
54    use super::*;
55
56    #[test]
57    fn option_fmap_some() {
58        let result = OptionF::fmap(Some(2), |x| x * 3);
59        assert_eq!(result, Some(6));
60    }
61
62    #[test]
63    fn option_fmap_none() {
64        let result = OptionF::fmap(None::<i32>, |x| x * 3);
65        assert_eq!(result, None);
66    }
67
68    #[test]
69    fn result_fmap_ok() {
70        let result = ResultF::<String>::fmap(Ok(5), |x| x + 1);
71        assert_eq!(result, Ok(6));
72    }
73
74    #[test]
75    fn result_fmap_err() {
76        let result = ResultF::<String>::fmap(Err("bad".to_string()), |x: i32| x + 1);
77        assert_eq!(result, Err("bad".to_string()));
78    }
79
80    #[test]
81    fn vec_fmap() {
82        let result = crate::hkt::VecF::fmap(vec![1, 2, 3], |x| x * 2);
83        assert_eq!(result, vec![2, 4, 6]);
84    }
85}
86
87#[cfg(test)]
88mod law_tests {
89    use super::*;
90    use proptest::prelude::*;
91
92    // Identity law: fmap(id, fa) == fa
93    // Composition law: fmap(g . f, fa) == fmap(g, fmap(f, fa))
94
95    proptest! {
96        #[test]
97        fn option_identity(x in any::<Option<i32>>()) {
98            let result = OptionF::fmap(x.clone(), |a| a);
99            prop_assert_eq!(result, x);
100        }
101
102        #[test]
103        fn option_composition(x in any::<Option<i32>>()) {
104            let f = |a: i32| a.wrapping_add(1);
105            let g = |a: i32| a.wrapping_mul(2);
106            let left = OptionF::fmap(x.clone(), |a| g(f(a)));
107            let right = OptionF::fmap(OptionF::fmap(x, f), g);
108            prop_assert_eq!(left, right);
109        }
110
111        #[test]
112        fn result_identity(x in any::<Result<i32, u8>>()) {
113            let result = ResultF::<u8>::fmap(x.clone(), |a| a);
114            prop_assert_eq!(result, x);
115        }
116
117        #[test]
118        fn result_composition(x in any::<Result<i32, u8>>()) {
119            let f = |a: i32| a.wrapping_add(1);
120            let g = |a: i32| a.wrapping_mul(2);
121            let left = ResultF::<u8>::fmap(x.clone(), |a| g(f(a)));
122            let right = ResultF::<u8>::fmap(ResultF::<u8>::fmap(x, f), g);
123            prop_assert_eq!(left, right);
124        }
125
126        #[test]
127        fn vec_identity(x in prop::collection::vec(any::<i32>(), 0..20)) {
128            let result = crate::hkt::VecF::fmap(x.clone(), |a| a);
129            prop_assert_eq!(result, x);
130        }
131
132        #[test]
133        fn vec_composition(x in prop::collection::vec(any::<i32>(), 0..20)) {
134            let f = |a: i32| a.wrapping_add(1);
135            let g = |a: i32| a.wrapping_mul(2);
136            let left = crate::hkt::VecF::fmap(x.clone(), |a| g(f(a)));
137            let right = crate::hkt::VecF::fmap(crate::hkt::VecF::fmap(x, f), g);
138            prop_assert_eq!(left, right);
139        }
140    }
141}