Skip to main content

karpal_core/
comonad.rs

1use crate::extend::Extend;
2use crate::hkt::{EnvF, IdentityF, OptionF};
3#[cfg(any(feature = "std", feature = "alloc"))]
4use crate::hkt::{NonEmptyVec, NonEmptyVecF};
5
6/// Comonad: the categorical dual of Monad.
7///
8/// A Comonad can `extract` a value from a context, and `extend` a
9/// context-aware function over the entire structure.
10///
11/// Laws:
12/// - Left identity: `extract(&extend(w, f)) == f(&w)`
13/// - Right identity: `extend(w, |w| extract(w)) == w`
14/// - Associativity (inherited from Extend)
15pub trait Comonad: Extend {
16    fn extract<A: Clone>(wa: &Self::Of<A>) -> A;
17}
18
19impl Comonad for IdentityF {
20    fn extract<A: Clone>(wa: &A) -> A {
21        wa.clone()
22    }
23}
24
25impl Comonad for OptionF {
26    fn extract<A: Clone>(wa: &Option<A>) -> A {
27        wa.as_ref()
28            .expect("Comonad::extract called on None")
29            .clone()
30    }
31}
32
33#[cfg(any(feature = "std", feature = "alloc"))]
34impl Comonad for NonEmptyVecF {
35    fn extract<A: Clone>(wa: &NonEmptyVec<A>) -> A {
36        wa.head.clone()
37    }
38}
39
40impl<E> Comonad for EnvF<E> {
41    fn extract<A: Clone>(wa: &(E, A)) -> A {
42        wa.1.clone()
43    }
44}
45
46#[cfg(test)]
47mod tests {
48    use super::*;
49
50    #[test]
51    fn identity_extract() {
52        assert_eq!(IdentityF::extract(&42), 42);
53    }
54
55    #[test]
56    fn option_extract() {
57        assert_eq!(OptionF::extract(&Some(42)), 42);
58    }
59
60    #[test]
61    #[should_panic(expected = "Comonad::extract called on None")]
62    fn option_extract_none_panics() {
63        OptionF::extract(&None::<i32>);
64    }
65
66    #[test]
67    fn nonemptyvec_extract() {
68        let nev = NonEmptyVec::new(1, vec![2, 3]);
69        assert_eq!(NonEmptyVecF::extract(&nev), 1);
70    }
71
72    #[test]
73    fn env_extract() {
74        assert_eq!(EnvF::<&str>::extract(&("hello", 42)), 42);
75    }
76}
77
78#[cfg(test)]
79mod law_tests {
80    use super::*;
81    use proptest::prelude::*;
82
83    fn nonemptyvec_strategy<T: core::fmt::Debug + Clone + 'static>(
84        elem: impl Strategy<Value = T> + Clone + 'static,
85    ) -> impl Strategy<Value = NonEmptyVec<T>> {
86        (elem.clone(), prop::collection::vec(elem, 0..5))
87            .prop_map(|(head, tail)| NonEmptyVec::new(head, tail))
88    }
89
90    proptest! {
91        // Left identity: extract(&extend(w, f)) == f(&w)
92        #[test]
93        fn identity_left_identity(x in any::<i32>()) {
94            let f = |w: &i32| w.wrapping_add(1);
95            let left = IdentityF::extract(&IdentityF::extend(x, f));
96            let right = f(&x);
97            prop_assert_eq!(left, right);
98        }
99
100        // Right identity: extend(w, |w| extract(w)) == w
101        #[test]
102        fn identity_right_identity(x in any::<i32>()) {
103            let result = IdentityF::extend(x, |w| IdentityF::extract(w));
104            prop_assert_eq!(result, x);
105        }
106
107        #[test]
108        fn nonemptyvec_left_identity(w in nonemptyvec_strategy(any::<i16>())) {
109            let f = |nev: &NonEmptyVec<i16>| nev.head.wrapping_add(1);
110            let left = NonEmptyVecF::extract(&NonEmptyVecF::extend(w.clone(), f));
111            let right = f(&w);
112            prop_assert_eq!(left, right);
113        }
114
115        #[test]
116        fn nonemptyvec_right_identity(w in nonemptyvec_strategy(any::<i16>())) {
117            let result = NonEmptyVecF::extend(w.clone(), |w| NonEmptyVecF::extract(w));
118            prop_assert_eq!(result, w);
119        }
120
121        #[test]
122        fn env_left_identity(e in any::<i8>(), a in any::<i16>()) {
123            let w = (e, a);
124            let f = |wa: &(i8, i16)| wa.1.wrapping_add(1);
125            let left = EnvF::<i8>::extract(&EnvF::<i8>::extend(w, f));
126            let right = f(&(e, a));
127            prop_assert_eq!(left, right);
128        }
129
130        #[test]
131        fn env_right_identity(e in any::<i8>(), a in any::<i16>()) {
132            let w = (e, a);
133            let result = EnvF::<i8>::extend(w, |w| EnvF::<i8>::extract(w));
134            prop_assert_eq!(result, (e, a));
135        }
136    }
137}