Skip to main content

karpal_core/
divide.rs

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