Skip to main content

karpal_core/
contravariant.rs

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