Skip to main content

karpal_core/
contravariant.rs

1use crate::hkt::HKT;
2#[cfg(all(not(feature = "std"), feature = "alloc"))]
3use alloc::boxed::Box;
4
5/// Contravariant functor: lifts a function `B -> A` into `F<A> -> F<B>`.
6///
7/// Laws:
8/// - Identity: `contramap(id, fa) == fa`
9/// - Composition: `contramap(f . g, fa) == contramap(g, contramap(f, fa))`
10pub trait Contravariant: HKT {
11    fn contramap<A: 'static, B>(fa: Self::Of<A>, f: impl Fn(B) -> A + 'static) -> Self::Of<B>;
12}
13
14/// Type constructor for predicates: `Of<A> = Box<dyn Fn(A) -> bool>`.
15#[cfg(any(feature = "std", feature = "alloc"))]
16pub struct PredicateF;
17
18#[cfg(any(feature = "std", feature = "alloc"))]
19impl HKT for PredicateF {
20    #[cfg(feature = "std")]
21    type Of<T> = Box<dyn Fn(T) -> bool>;
22
23    #[cfg(all(not(feature = "std"), feature = "alloc"))]
24    type Of<T> = alloc::boxed::Box<dyn Fn(T) -> bool>;
25}
26
27#[cfg(any(feature = "std", feature = "alloc"))]
28impl Contravariant for PredicateF {
29    fn contramap<A: 'static, B>(fa: Self::Of<A>, f: impl Fn(B) -> A + 'static) -> Self::Of<B> {
30        Box::new(move |b| fa(f(b)))
31    }
32}
33
34#[cfg(test)]
35mod tests {
36    use super::*;
37
38    #[test]
39    fn predicate_contramap() {
40        let is_positive: Box<dyn Fn(i32) -> bool> = Box::new(|x| x > 0);
41        let str_len_positive = PredicateF::contramap(is_positive, |s: &str| s.len() as i32);
42        assert!(str_len_positive("hello"));
43        assert!(!str_len_positive(""));
44    }
45}
46
47#[cfg(test)]
48mod law_tests {
49    use super::*;
50    use proptest::prelude::*;
51
52    proptest! {
53        // Identity: contramap(id, fa)(x) == fa(x)
54        #[test]
55        fn predicate_identity(x in any::<i32>()) {
56            let pred: Box<dyn Fn(i32) -> bool> = Box::new(|a| a > 0);
57            let expected = pred(x);
58            let result = PredicateF::contramap(pred, |a: i32| a);
59            prop_assert_eq!(result(x), expected);
60        }
61
62        // Composition: contramap(f . g, fa) == contramap(g, contramap(f, fa))
63        #[test]
64        fn predicate_composition(x in any::<i16>()) {
65            let pred: Box<dyn Fn(i32) -> bool> = Box::new(|a| a > 0);
66            let f = |a: i16| a as i32;
67            let g = |a: i16| a.wrapping_add(1);
68
69            // contramap(f . g, pred)
70            let pred1: Box<dyn Fn(i32) -> bool> = Box::new(|a| a > 0);
71            let left = PredicateF::contramap(pred1, move |a: i16| f(g(a)));
72
73            // contramap(g, contramap(f, pred))
74            let inner = PredicateF::contramap(pred, f);
75            let right = PredicateF::contramap(inner, g);
76
77            prop_assert_eq!(left(x), right(x));
78        }
79    }
80}