Skip to main content

karpal_arrow/
fn_arrow.rs

1use karpal_core::hkt::HKT2;
2
3use crate::arrow::Arrow;
4use crate::arrow_apply::ArrowApply;
5use crate::arrow_choice::ArrowChoice;
6use crate::arrow_loop::ArrowLoop;
7use crate::category::Category;
8use crate::semigroupoid::Semigroupoid;
9
10/// Marker type whose `P<A, B>` is `Box<dyn Fn(A) -> B>`.
11///
12/// This is the canonical Arrow instance: the function arrow.
13/// Equivalent to `FnP` in karpal-profunctor but independent (no cross-crate dep).
14pub struct FnA;
15
16impl HKT2 for FnA {
17    type P<A, B> = Box<dyn Fn(A) -> B>;
18}
19
20impl Semigroupoid for FnA {
21    fn compose<A: Clone + 'static, B: Clone + 'static, C: Clone + 'static>(
22        f: Box<dyn Fn(B) -> C>,
23        g: Box<dyn Fn(A) -> B>,
24    ) -> Box<dyn Fn(A) -> C> {
25        Box::new(move |a| f(g(a)))
26    }
27}
28
29impl Category for FnA {
30    fn id<A: Clone + 'static>() -> Box<dyn Fn(A) -> A> {
31        Box::new(|a| a)
32    }
33}
34
35impl Arrow for FnA {
36    fn arr<A: Clone + 'static, B: Clone + 'static>(
37        f: impl Fn(A) -> B + 'static,
38    ) -> Box<dyn Fn(A) -> B> {
39        Box::new(f)
40    }
41
42    fn first<A: Clone + 'static, B: Clone + 'static, C: Clone + 'static>(
43        pab: Box<dyn Fn(A) -> B>,
44    ) -> Box<dyn Fn((A, C)) -> (B, C)> {
45        Box::new(move |(a, c)| (pab(a), c))
46    }
47
48    fn second<A: Clone + 'static, B: Clone + 'static, C: Clone + 'static>(
49        pab: Box<dyn Fn(A) -> B>,
50    ) -> Box<dyn Fn((C, A)) -> (C, B)> {
51        Box::new(move |(c, a)| (c, pab(a)))
52    }
53}
54
55impl ArrowChoice for FnA {
56    fn left<A: Clone + 'static, B: Clone + 'static, C: Clone + 'static>(
57        pab: Box<dyn Fn(A) -> B>,
58    ) -> Box<dyn Fn(Result<A, C>) -> Result<B, C>> {
59        Box::new(move |r| match r {
60            Ok(a) => Ok(pab(a)),
61            Err(c) => Err(c),
62        })
63    }
64
65    fn right<A: Clone + 'static, B: Clone + 'static, C: Clone + 'static>(
66        pab: Box<dyn Fn(A) -> B>,
67    ) -> Box<dyn Fn(Result<C, A>) -> Result<C, B>> {
68        Box::new(move |r| match r {
69            Ok(c) => Ok(c),
70            Err(a) => Err(pab(a)),
71        })
72    }
73}
74
75impl ArrowApply for FnA {
76    fn app<A: Clone + 'static, B: Clone + 'static>() -> Box<dyn Fn((Box<dyn Fn(A) -> B>, A)) -> B> {
77        Box::new(|(f, a)| f(a))
78    }
79}
80
81impl ArrowLoop for FnA {
82    fn loop_arrow<A: Clone + 'static, B: Clone + 'static, D: Default + Clone + 'static>(
83        f: Box<dyn Fn((A, D)) -> (B, D)>,
84    ) -> Box<dyn Fn(A) -> B> {
85        Box::new(move |a| {
86            let (b, _d) = f((a, D::default()));
87            b
88        })
89    }
90}
91
92#[cfg(test)]
93mod tests {
94    use super::*;
95
96    #[test]
97    fn fna_compose() {
98        let f: Box<dyn Fn(i32) -> i32> = Box::new(|x| x + 1);
99        let g: Box<dyn Fn(i32) -> i32> = Box::new(|x| x * 2);
100        let fg = FnA::compose(f, g);
101        assert_eq!(fg(3), 7); // (3 * 2) + 1
102    }
103
104    #[test]
105    fn fna_id() {
106        let id = FnA::id::<i32>();
107        assert_eq!(id(42), 42);
108    }
109
110    #[test]
111    fn fna_arr() {
112        let f = FnA::arr(|x: i32| x.to_string());
113        assert_eq!(f(42), "42");
114    }
115
116    #[test]
117    fn fna_first() {
118        let double: Box<dyn Fn(i32) -> i32> = Box::new(|x| x * 2);
119        let f = FnA::first::<i32, i32, &str>(double);
120        assert_eq!(f((5, "hi")), (10, "hi"));
121    }
122
123    #[test]
124    fn fna_second() {
125        let double: Box<dyn Fn(i32) -> i32> = Box::new(|x| x * 2);
126        let f = FnA::second::<i32, i32, &str>(double);
127        assert_eq!(f(("hi", 5)), ("hi", 10));
128    }
129
130    #[test]
131    fn fna_split() {
132        let double: Box<dyn Fn(i32) -> i32> = Box::new(|x| x * 2);
133        let negate: Box<dyn Fn(i32) -> i32> = Box::new(|x| -x);
134        let f = FnA::split(double, negate);
135        assert_eq!(f((3, 4)), (6, -4));
136    }
137
138    #[test]
139    fn fna_fanout() {
140        let double: Box<dyn Fn(i32) -> i32> = Box::new(|x| x * 2);
141        let negate: Box<dyn Fn(i32) -> i32> = Box::new(|x| -x);
142        let f = FnA::fanout(double, negate);
143        assert_eq!(f(5), (10, -5));
144    }
145
146    #[test]
147    fn fna_left() {
148        let double: Box<dyn Fn(i32) -> i32> = Box::new(|x| x * 2);
149        let f = FnA::left::<i32, i32, &str>(double);
150        assert_eq!(f(Ok(5)), Ok(10));
151        assert_eq!(f(Err("nope")), Err("nope"));
152    }
153
154    #[test]
155    fn fna_right() {
156        let double: Box<dyn Fn(i32) -> i32> = Box::new(|x| x * 2);
157        let f = FnA::right::<i32, i32, &str>(double);
158        assert_eq!(f(Err(5)), Err(10));
159        assert_eq!(f(Ok("yep")), Ok("yep"));
160    }
161
162    #[test]
163    fn fna_splat() {
164        let double: Box<dyn Fn(i32) -> i32> = Box::new(|x| x * 2);
165        let len: Box<dyn Fn(String) -> usize> = Box::new(|s| s.len());
166        let f = FnA::splat(double, len);
167        assert_eq!(f(Ok(5)), Ok(10));
168        assert_eq!(f(Err("hello".to_string())), Err(5));
169    }
170
171    #[test]
172    fn fna_fanin() {
173        let double: Box<dyn Fn(i32) -> String> = Box::new(|x| format!("int:{}", x));
174        let show: Box<dyn Fn(bool) -> String> = Box::new(|b| format!("bool:{}", b));
175        let f = FnA::fanin(double, show);
176        assert_eq!(f(Ok(42)), "int:42");
177        assert_eq!(f(Err(true)), "bool:true");
178    }
179
180    #[test]
181    fn fna_app() {
182        let app = FnA::app::<i32, i32>();
183        let double: Box<dyn Fn(i32) -> i32> = Box::new(|x| x * 2);
184        assert_eq!(app((double, 5)), 10);
185    }
186
187    #[test]
188    fn fna_loop_arrow() {
189        // loop_arrow feeds D::default() as the feedback value
190        let f = FnA::loop_arrow::<i32, i32, i32>(Box::new(|(a, d)| (a + d, d)));
191        assert_eq!(f(5), 5); // 5 + 0 (i32::default() == 0)
192    }
193}
194
195#[cfg(test)]
196mod law_tests {
197    use super::*;
198    use proptest::prelude::*;
199
200    proptest! {
201        // Semigroupoid associativity: compose(f, compose(g, h)) == compose(compose(f, g), h)
202        #[test]
203        fn associativity(x in any::<i16>()) {
204            let f = || -> Box<dyn Fn(i16) -> i16> { Box::new(|a| a.wrapping_add(1)) };
205            let g = || -> Box<dyn Fn(i16) -> i16> { Box::new(|a| a.wrapping_mul(2)) };
206            let h = || -> Box<dyn Fn(i16) -> i16> { Box::new(|a| a.wrapping_sub(3)) };
207
208            let left = FnA::compose(f(), FnA::compose(g(), h()));
209            let right = FnA::compose(FnA::compose(f(), g()), h());
210            prop_assert_eq!(left(x), right(x));
211        }
212
213        // Category left identity: compose(id(), f) == f
214        #[test]
215        fn left_identity(x in any::<i16>()) {
216            let f = || -> Box<dyn Fn(i16) -> i16> { Box::new(|a| a.wrapping_mul(2)) };
217            let left = FnA::compose(FnA::id(), f());
218            let right = f();
219            prop_assert_eq!(left(x), right(x));
220        }
221
222        // Category right identity: compose(f, id()) == f
223        #[test]
224        fn right_identity(x in any::<i16>()) {
225            let f = || -> Box<dyn Fn(i16) -> i16> { Box::new(|a| a.wrapping_mul(2)) };
226            let left = FnA::compose(f(), FnA::id());
227            let right = f();
228            prop_assert_eq!(left(x), right(x));
229        }
230
231        // Arrow: arr(id) == id()
232        #[test]
233        fn arr_id(x in any::<i16>()) {
234            let left = FnA::arr(|a: i16| a);
235            let right = FnA::id::<i16>();
236            prop_assert_eq!(left(x), right(x));
237        }
238
239        // Arrow: arr(g . f) == compose(arr(g), arr(f))
240        #[test]
241        fn arr_composition(x in any::<i16>()) {
242            let f = |a: i16| a.wrapping_add(1);
243            let g = |a: i16| a.wrapping_mul(2);
244
245            let left = FnA::arr(move |a: i16| g(f(a)));
246            let right = FnA::compose(FnA::arr(g), FnA::arr(f));
247            prop_assert_eq!(left(x), right(x));
248        }
249
250        // Arrow: first(arr(f)) == arr(|(a, c)| (f(a), c))
251        #[test]
252        fn first_arr(x in any::<i16>(), c in any::<i16>()) {
253            let f = |a: i16| a.wrapping_add(1);
254            let left = FnA::first::<i16, i16, i16>(FnA::arr(f));
255            let right = FnA::arr(move |(a, c): (i16, i16)| (f(a), c));
256            prop_assert_eq!(left((x, c)), right((x, c)));
257        }
258
259        // Arrow: first(compose(f, g)) == compose(first(f), first(g))
260        #[test]
261        fn first_compose(x in any::<i16>(), c in any::<i16>()) {
262            let f = || -> Box<dyn Fn(i16) -> i16> { Box::new(|a| a.wrapping_add(1)) };
263            let g = || -> Box<dyn Fn(i16) -> i16> { Box::new(|a| a.wrapping_mul(2)) };
264
265            let left = FnA::first::<i16, i16, i16>(FnA::compose(f(), g()));
266            let right = FnA::compose(
267                FnA::first::<i16, i16, i16>(f()),
268                FnA::first::<i16, i16, i16>(g()),
269            );
270            prop_assert_eq!(left((x, c)), right((x, c)));
271        }
272
273        // ArrowChoice: left(arr(f)) == arr(|r| r.map(f))
274        #[test]
275        fn left_arr(x in any::<Result<i16, i16>>()) {
276            let f = |a: i16| a.wrapping_add(1);
277            let left = FnA::left::<i16, i16, i16>(FnA::arr(f));
278            let right = FnA::arr(move |r: Result<i16, i16>| r.map(f));
279            prop_assert_eq!(left(x), right(x));
280        }
281    }
282}