Skip to main content

karpal_core/
bifunctor.rs

1use crate::hkt::{HKT2, ResultBF, TupleF};
2
3/// Bifunctor: maps over both type parameters of a two-parameter type constructor.
4///
5/// Laws:
6/// - Identity: `bimap(id, id, fab) == fab`
7/// - Composition: `bimap(f . g, h . i, fab) == bimap(f, h, bimap(g, i, fab))`
8pub trait Bifunctor: HKT2 {
9    fn bimap<A, B, C, D>(
10        fab: Self::P<A, B>,
11        f: impl Fn(A) -> C,
12        g: impl Fn(B) -> D,
13    ) -> Self::P<C, D>;
14
15    fn first<A, B, C>(fab: Self::P<A, B>, f: impl Fn(A) -> C) -> Self::P<C, B> {
16        Self::bimap(fab, f, |b| b)
17    }
18
19    fn second<A, B, D>(fab: Self::P<A, B>, g: impl Fn(B) -> D) -> Self::P<A, D> {
20        Self::bimap(fab, |a| a, g)
21    }
22}
23
24impl Bifunctor for ResultBF {
25    fn bimap<A, B, C, D>(
26        fab: Result<B, A>,
27        f: impl Fn(A) -> C,
28        g: impl Fn(B) -> D,
29    ) -> Result<D, C> {
30        match fab {
31            Ok(b) => Ok(g(b)),
32            Err(a) => Err(f(a)),
33        }
34    }
35}
36
37impl Bifunctor for TupleF {
38    fn bimap<A, B, C, D>(fab: (A, B), f: impl Fn(A) -> C, g: impl Fn(B) -> D) -> (C, D) {
39        (f(fab.0), g(fab.1))
40    }
41}
42
43#[cfg(test)]
44mod tests {
45    use super::*;
46
47    #[test]
48    fn result_bimap_ok() {
49        let r: Result<i32, &str> = Ok(5);
50        let result = ResultBF::bimap(r, |s| s.len(), |n| n * 2);
51        assert_eq!(result, Ok(10));
52    }
53
54    #[test]
55    fn result_bimap_err() {
56        let r: Result<i32, &str> = Err("hello");
57        let result = ResultBF::bimap(r, |s| s.len(), |n| n * 2);
58        assert_eq!(result, Err(5));
59    }
60
61    #[test]
62    fn result_first() {
63        let r: Result<i32, &str> = Err("hi");
64        let result = ResultBF::first(r, |s| s.len());
65        assert_eq!(result, Err(2));
66    }
67
68    #[test]
69    fn result_second() {
70        let r: Result<i32, &str> = Ok(5);
71        let result = ResultBF::second(r, |n| n * 3);
72        assert_eq!(result, Ok(15));
73    }
74
75    #[test]
76    fn tuple_bimap() {
77        assert_eq!(TupleF::bimap((1, "hi"), |x| x + 1, |s| s.len()), (2, 2));
78    }
79
80    #[test]
81    fn tuple_first() {
82        assert_eq!(TupleF::first((1, "hi"), |x| x * 2), (2, "hi"));
83    }
84
85    #[test]
86    fn tuple_second() {
87        assert_eq!(TupleF::second((1, "hi"), |s| s.len()), (1, 2));
88    }
89}
90
91#[cfg(test)]
92mod law_tests {
93    use super::*;
94    use proptest::prelude::*;
95
96    proptest! {
97        // Identity: bimap(id, id, fab) == fab
98        #[test]
99        fn tuple_identity(a in any::<i32>(), b in any::<i32>()) {
100            let fab = (a, b);
101            let result = TupleF::bimap(fab, |x| x, |y| y);
102            prop_assert_eq!(result, (a, b));
103        }
104
105        // Composition: bimap(f . g, h . i, fab) == bimap(f, h, bimap(g, i, fab))
106        #[test]
107        fn tuple_composition(a in any::<i16>(), b in any::<i16>()) {
108            let fab = (a, b);
109            let f = |x: i16| x.wrapping_add(1);
110            let g = |x: i16| x.wrapping_mul(2);
111            let h = |x: i16| x.wrapping_add(3);
112            let i_fn = |x: i16| x.wrapping_mul(4);
113
114            let left = TupleF::bimap(fab, |x| f(g(x)), |y| h(i_fn(y)));
115            let right = TupleF::bimap(TupleF::bimap(fab, g, i_fn), f, h);
116            prop_assert_eq!(left, right);
117        }
118
119        #[test]
120        fn result_identity(x in any::<Result<i32, i32>>()) {
121            let result = ResultBF::bimap(x, |a| a, |b| b);
122            prop_assert_eq!(result, x);
123        }
124
125        #[test]
126        fn result_composition(x in any::<Result<i16, i16>>()) {
127            let f = |x: i16| x.wrapping_add(1);
128            let g = |x: i16| x.wrapping_mul(2);
129            let h = |x: i16| x.wrapping_add(3);
130            let i_fn = |x: i16| x.wrapping_mul(4);
131
132            let left = ResultBF::bimap(x, |a| f(g(a)), |b| h(i_fn(b)));
133            let right = ResultBF::bimap(ResultBF::bimap(x, g, i_fn), f, h);
134            prop_assert_eq!(left, right);
135        }
136    }
137}