Skip to main content

karpal_core/
chain.rs

1use crate::apply::Apply;
2use crate::hkt::OptionF;
3use crate::hkt::ResultF;
4#[cfg(any(feature = "std", feature = "alloc"))]
5use crate::hkt::VecF;
6#[cfg(all(not(feature = "std"), feature = "alloc"))]
7use alloc::vec::Vec;
8
9/// Chain (FlatMap): an Apply with monadic bind.
10///
11/// Laws:
12/// - Associativity: `chain(chain(m, f), g) == chain(m, |x| chain(f(x), g))`
13pub trait Chain: Apply {
14    fn chain<A, B>(fa: Self::Of<A>, f: impl Fn(A) -> Self::Of<B>) -> Self::Of<B>;
15}
16
17impl Chain for OptionF {
18    fn chain<A, B>(fa: Option<A>, f: impl Fn(A) -> Option<B>) -> Option<B> {
19        fa.and_then(f)
20    }
21}
22
23impl<E> Chain for ResultF<E> {
24    fn chain<A, B>(fa: Result<A, E>, f: impl Fn(A) -> Result<B, E>) -> Result<B, E> {
25        fa.and_then(f)
26    }
27}
28
29#[cfg(any(feature = "std", feature = "alloc"))]
30impl Chain for VecF {
31    fn chain<A, B>(fa: Vec<A>, f: impl Fn(A) -> Vec<B>) -> Vec<B> {
32        fa.into_iter().flat_map(f).collect()
33    }
34}
35
36impl Chain for crate::hkt::IdentityF {
37    fn chain<A, B>(fa: A, f: impl Fn(A) -> B) -> B {
38        f(fa)
39    }
40}
41
42#[cfg(any(feature = "std", feature = "alloc"))]
43impl Chain for crate::hkt::NonEmptyVecF {
44    fn chain<A, B>(
45        fa: crate::hkt::NonEmptyVec<A>,
46        f: impl Fn(A) -> crate::hkt::NonEmptyVec<B>,
47    ) -> crate::hkt::NonEmptyVec<B> {
48        let first = f(fa.head);
49        let mut tail = first.tail;
50        for a in fa.tail {
51            let result = f(a);
52            tail.push(result.head);
53            tail.extend(result.tail);
54        }
55        crate::hkt::NonEmptyVec::new(first.head, tail)
56    }
57}
58
59#[cfg(test)]
60mod tests {
61    use super::*;
62
63    #[test]
64    fn option_chain_some() {
65        let result = OptionF::chain(Some(3), |x| if x > 0 { Some(x * 2) } else { None });
66        assert_eq!(result, Some(6));
67    }
68
69    #[test]
70    fn option_chain_none() {
71        let result = OptionF::chain(None::<i32>, |x| Some(x * 2));
72        assert_eq!(result, None);
73    }
74
75    #[test]
76    fn result_chain_ok() {
77        let result = ResultF::<&str>::chain(Ok(3), |x| Ok(x + 1));
78        assert_eq!(result, Ok(4));
79    }
80
81    #[test]
82    fn result_chain_err() {
83        let result = ResultF::<&str>::chain(Err("bad"), |x: i32| Ok(x + 1));
84        assert_eq!(result, Err("bad"));
85    }
86
87    #[test]
88    fn vec_chain() {
89        let result = VecF::chain(vec![1, 2, 3], |x| vec![x, x * 10]);
90        assert_eq!(result, vec![1, 10, 2, 20, 3, 30]);
91    }
92}
93
94#[cfg(test)]
95mod law_tests {
96    use super::*;
97    use proptest::prelude::*;
98
99    proptest! {
100        // Associativity: chain(chain(m, f), g) == chain(m, |x| chain(f(x), g))
101        #[test]
102        fn option_associativity(x in any::<i16>()) {
103            let m = Some(x);
104            let f = |a: i16| Some(a.wrapping_add(1));
105            let g = |a: i16| Some(a.wrapping_mul(2));
106
107            let left = OptionF::chain(OptionF::chain(m, f), g);
108            let right = OptionF::chain(m, |a| OptionF::chain(f(a), g));
109            prop_assert_eq!(left, right);
110        }
111
112        #[test]
113        fn vec_associativity(x in prop::collection::vec(any::<i8>(), 0..5)) {
114            let f = |a: i8| vec![a, a.wrapping_add(1)];
115            let g = |a: i8| vec![a.wrapping_mul(2)];
116
117            let left = VecF::chain(VecF::chain(x.clone(), f), g);
118            let right = VecF::chain(x, |a| VecF::chain(f(a), g));
119            prop_assert_eq!(left, right);
120        }
121    }
122}