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