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