Skip to main content

karpal_core/
divide.rs

1use crate::contravariant::{Contravariant, PredicateF};
2
3/// Divide: the contravariant analogue of Apply.
4///
5/// Given a way to split `C` into `(A, B)`, and contravariant functors over
6/// `A` and `B`, produce a contravariant functor over `C`.
7///
8/// Laws:
9/// - Associativity: `divide(f, divide(g, a, b), c) == divide(h, a, divide(i, b, c))`
10///   (where f/g/h/i are appropriate splitting functions)
11pub trait Divide: Contravariant {
12    fn divide<A: 'static, B: 'static, C: 'static>(
13        f: impl Fn(C) -> (A, B) + 'static,
14        fa: Self::Of<A>,
15        fb: Self::Of<B>,
16    ) -> Self::Of<C>;
17}
18
19impl Divide for PredicateF {
20    fn divide<A: 'static, B: 'static, C: 'static>(
21        f: impl Fn(C) -> (A, B) + 'static,
22        fa: Box<dyn Fn(A) -> bool>,
23        fb: Box<dyn Fn(B) -> bool>,
24    ) -> Box<dyn Fn(C) -> bool> {
25        Box::new(move |c| {
26            let (a, b) = f(c);
27            fa(a) && fb(b)
28        })
29    }
30}
31
32#[cfg(test)]
33mod tests {
34    use super::*;
35
36    #[test]
37    fn predicate_divide() {
38        let is_positive: Box<dyn Fn(i32) -> bool> = Box::new(|x| x > 0);
39        let is_even: Box<dyn Fn(i32) -> bool> = Box::new(|x| x % 2 == 0);
40
41        // Split a tuple into its components, check both predicates
42        let both: Box<dyn Fn((i32, i32)) -> bool> =
43            PredicateF::divide(|pair: (i32, i32)| pair, is_positive, is_even);
44
45        assert!(both((3, 4))); // 3 > 0 && 4 is even
46        assert!(!both((-1, 4))); // -1 not > 0
47        assert!(!both((3, 3))); // 3 is not even
48    }
49}
50
51#[cfg(test)]
52mod law_tests {
53    use super::*;
54    use proptest::prelude::*;
55
56    proptest! {
57        // Associativity (simplified): testing that divide composes correctly
58        #[test]
59        fn predicate_associativity(x in any::<i8>(), y in any::<i8>(), z in any::<i8>()) {
60            let pa: Box<dyn Fn(i8) -> bool> = Box::new(|a| a > 0);
61            let pb: Box<dyn Fn(i8) -> bool> = Box::new(|b| b > 0);
62            let pc: Box<dyn Fn(i8) -> bool> = Box::new(|c| c > 0);
63
64            // divide(id, divide(id, a, b), c) applied to (x, y, z)
65            let pa2: Box<dyn Fn(i8) -> bool> = Box::new(|a| a > 0);
66            let pb2: Box<dyn Fn(i8) -> bool> = Box::new(|b| b > 0);
67            let pc2: Box<dyn Fn(i8) -> bool> = Box::new(|c| c > 0);
68
69            let ab = PredicateF::divide(|pair: (i8, i8)| pair, pa, pb);
70            let left = PredicateF::divide(
71                |triple: (i8, i8, i8)| ((triple.0, triple.1), triple.2),
72                ab,
73                pc,
74            );
75
76            let bc = PredicateF::divide(|pair: (i8, i8)| pair, pb2, pc2);
77            let right = PredicateF::divide(
78                |triple: (i8, i8, i8)| (triple.0, (triple.1, triple.2)),
79                pa2,
80                bc,
81            );
82
83            prop_assert_eq!(left((x, y, z)), right((x, y, z)));
84        }
85    }
86}