Skip to main content

karpal_core/
contravariant.rs

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