Skip to main content

karpal_core/
bifunctor.rs

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