Skip to main content

karpal_core/
decide.rs

1use crate::contravariant::{Contravariant, PredicateF};
2
3/// Decide: the contravariant analogue of Alt.
4///
5/// Given a way to split `C` into either `A` or `B`, and contravariant
6/// functors over `A` and `B`, produce a contravariant functor over `C`.
7///
8/// Laws:
9/// - Associativity: `choose(f, choose(g, a, b), c) == choose(h, a, choose(i, b, c))`
10///   (where f/g/h/i are appropriate splitting functions)
11pub trait Decide: Contravariant {
12    fn choose<A: 'static, B: 'static, C: 'static>(
13        f: impl Fn(C) -> Result<A, B> + 'static,
14        fa: Self::Of<A>,
15        fb: Self::Of<B>,
16    ) -> Self::Of<C>;
17}
18
19impl Decide for PredicateF {
20    fn choose<A: 'static, B: 'static, C: 'static>(
21        f: impl Fn(C) -> Result<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| match f(c) {
26            Ok(a) => fa(a),
27            Err(b) => fb(b),
28        })
29    }
30}
31
32#[cfg(test)]
33mod tests {
34    use super::*;
35
36    #[test]
37    fn predicate_choose() {
38        let is_positive: Box<dyn Fn(i32) -> bool> = Box::new(|x| x > 0);
39        let is_short: Box<dyn Fn(String) -> bool> = Box::new(|s| s.len() < 5);
40
41        // Classify input: integers go left, strings go right
42        let classifier =
43            PredicateF::choose(|input: Result<i32, String>| input, is_positive, is_short);
44
45        assert!(classifier(Ok(5)));
46        assert!(!classifier(Ok(-1)));
47        assert!(classifier(Err("hi".to_string())));
48        assert!(!classifier(Err("hello world".to_string())));
49    }
50}
51
52#[cfg(test)]
53mod law_tests {
54    use super::*;
55    use proptest::prelude::*;
56
57    proptest! {
58        // Associativity
59        #[test]
60        fn predicate_associativity(x in any::<i8>(), y in any::<i8>(), z in any::<i8>()) {
61            let pa: Box<dyn Fn(i8) -> bool> = Box::new(|a| a > 0);
62            let pb: Box<dyn Fn(i8) -> bool> = Box::new(|b| b > 0);
63            let pc: Box<dyn Fn(i8) -> bool> = Box::new(|c| c > 0);
64
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            // choose(id, choose(id, a, b), c) on a tagged union
70            // Tag: 0 = a, 1 = b, 2 = c
71            let ab = PredicateF::choose(|v: Result<i8, i8>| v, pa, pb);
72            let left = PredicateF::choose(
73                |tag: (u8, i8)| {
74                    if tag.0 < 2 {
75                        Ok(if tag.0 == 0 { Ok(tag.1) } else { Err(tag.1) })
76                    } else {
77                        Err(tag.1)
78                    }
79                },
80                ab,
81                pc,
82            );
83
84            let bc = PredicateF::choose(|v: Result<i8, i8>| v, pb2, pc2);
85            let right = PredicateF::choose(
86                |tag: (u8, i8)| {
87                    if tag.0 == 0 {
88                        Ok(tag.1)
89                    } else {
90                        Err(if tag.0 == 1 { Ok(tag.1) } else { Err(tag.1) })
91                    }
92                },
93                pa2,
94                bc,
95            );
96
97            // Test all three tags
98            prop_assert_eq!(left((0, x)), right((0, x)));
99            prop_assert_eq!(left((1, y)), right((1, y)));
100            prop_assert_eq!(left((2, z)), right((2, z)));
101        }
102    }
103}