Skip to main content

karpal_core/
apply.rs

1use crate::functor::Functor;
2use crate::hkt::OptionF;
3use crate::hkt::ResultF;
4#[cfg(any(feature = "std", feature = "alloc"))]
5use crate::hkt::VecF;
6
7/// Apply: a Functor that can apply a wrapped function to a wrapped value.
8///
9/// Laws:
10/// - Composition: `ap(ap(fmap(compose, f), g), x) == ap(f, ap(g, x))`
11pub trait Apply: Functor {
12    fn ap<A, B, F>(ff: Self::Of<F>, fa: Self::Of<A>) -> Self::Of<B>
13    where
14        A: Clone,
15        F: Fn(A) -> B;
16}
17
18impl Apply for OptionF {
19    fn ap<A, B, F>(ff: Option<F>, fa: Option<A>) -> Option<B>
20    where
21        A: Clone,
22        F: Fn(A) -> B,
23    {
24        match (ff, fa) {
25            (Some(f), Some(a)) => Some(f(a)),
26            _ => None,
27        }
28    }
29}
30
31impl<E> Apply for ResultF<E> {
32    fn ap<A, B, F>(ff: Result<F, E>, fa: Result<A, E>) -> Result<B, E>
33    where
34        A: Clone,
35        F: Fn(A) -> B,
36    {
37        match (ff, fa) {
38            (Ok(f), Ok(a)) => Ok(f(a)),
39            (Err(e), _) => Err(e),
40            (_, Err(e)) => Err(e),
41        }
42    }
43}
44
45#[cfg(any(feature = "std", feature = "alloc"))]
46impl Apply for VecF {
47    fn ap<A, B, F>(ff: Vec<F>, fa: Vec<A>) -> Vec<B>
48    where
49        A: Clone,
50        F: Fn(A) -> B,
51    {
52        let mut result = Vec::with_capacity(ff.len() * fa.len());
53        for f in &ff {
54            for a in &fa {
55                result.push(f(a.clone()));
56            }
57        }
58        result
59    }
60}
61
62impl Apply for crate::hkt::IdentityF {
63    fn ap<A, B, F>(ff: F, fa: A) -> B
64    where
65        A: Clone,
66        F: Fn(A) -> B,
67    {
68        ff(fa)
69    }
70}
71
72#[cfg(any(feature = "std", feature = "alloc"))]
73impl Apply for crate::hkt::NonEmptyVecF {
74    fn ap<A, B, F>(
75        ff: crate::hkt::NonEmptyVec<F>,
76        fa: crate::hkt::NonEmptyVec<A>,
77    ) -> crate::hkt::NonEmptyVec<B>
78    where
79        A: Clone,
80        F: Fn(A) -> B,
81    {
82        // Apply each function to each value (cartesian product)
83        let head = (ff.head)(fa.head.clone());
84        let mut tail = Vec::new();
85        for a in &fa.tail {
86            tail.push((ff.head)(a.clone()));
87        }
88        for f in &ff.tail {
89            tail.push(f(fa.head.clone()));
90            for a in &fa.tail {
91                tail.push(f(a.clone()));
92            }
93        }
94        crate::hkt::NonEmptyVec::new(head, tail)
95    }
96}
97
98#[cfg(test)]
99mod tests {
100    use super::*;
101
102    #[test]
103    fn option_ap_some() {
104        let f: Option<fn(i32) -> i32> = Some(|x| x * 2);
105        assert_eq!(OptionF::ap(f, Some(3)), Some(6));
106    }
107
108    #[test]
109    fn option_ap_none_fn() {
110        let f: Option<fn(i32) -> i32> = None;
111        assert_eq!(OptionF::ap(f, Some(3)), None);
112    }
113
114    #[test]
115    fn option_ap_none_val() {
116        let f: Option<fn(i32) -> i32> = Some(|x| x * 2);
117        assert_eq!(OptionF::ap(f, None), None);
118    }
119
120    #[test]
121    fn result_ap_ok() {
122        let f: Result<fn(i32) -> i32, &str> = Ok(|x| x + 1);
123        assert_eq!(ResultF::<&str>::ap(f, Ok(5)), Ok(6));
124    }
125
126    #[test]
127    fn result_ap_err() {
128        let f: Result<fn(i32) -> i32, &str> = Err("bad");
129        assert_eq!(ResultF::<&str>::ap(f, Ok(5)), Err("bad"));
130    }
131
132    #[test]
133    fn vec_ap() {
134        let fs: Vec<fn(i32) -> i32> = vec![|x| x + 1, |x| x * 10];
135        assert_eq!(VecF::ap(fs, vec![1, 2, 3]), vec![2, 3, 4, 10, 20, 30]);
136    }
137}
138
139#[cfg(test)]
140mod law_tests {
141    use super::*;
142    use proptest::prelude::*;
143
144    // Composition law: ap(ap(fmap(compose, f), g), x) == ap(f, ap(g, x))
145    // Simplified test: ap(fmap(compose, f), g) applied to x
146    proptest! {
147        #[test]
148        fn option_composition(x in any::<i16>()) {
149            let f: Option<fn(i16) -> i16> = Some(|a| a.wrapping_add(1));
150            let g: Option<fn(i16) -> i16> = Some(|a| a.wrapping_mul(2));
151
152            // ap(f, ap(g, x))
153            let right = OptionF::ap(f, OptionF::ap(g, Some(x)));
154
155            // ap(ap(fmap(compose, f), g), x) where compose = |f| |g| |x| f(g(x))
156            let composed: Option<fn(i16) -> i16> = Some(|a| a.wrapping_mul(2).wrapping_add(1));
157            let left = OptionF::ap(composed, Some(x));
158
159            prop_assert_eq!(left, right);
160        }
161
162        #[test]
163        fn vec_composition(x in prop::collection::vec(any::<i8>(), 0..5)) {
164            let f: Vec<fn(i8) -> i8> = vec![|a| a.wrapping_add(1)];
165            let g: Vec<fn(i8) -> i8> = vec![|a| a.wrapping_mul(2)];
166
167            let right = VecF::ap(f, VecF::ap(g, x.clone()));
168
169            let composed: Vec<fn(i8) -> i8> = vec![|a| a.wrapping_mul(2).wrapping_add(1)];
170            let left = VecF::ap(composed, x);
171
172            prop_assert_eq!(left, right);
173        }
174    }
175}