Skip to main content

karpal_core/
divide.rs

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