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