Skip to main content

karpal_core/
apply.rs

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