Skip to main content

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