Skip to main content

karpal_core/
decide.rs

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