rust_overture/
chain.rs

1/// Variadic-style macro for chaining functions that return Option, Result, or Vec.
2#[macro_export]
3macro_rules! chain {
4    // base case: just one function
5    ($f:expr) => {
6        $f
7    };
8
9    // recursive case: f, g, ...
10    ($f:expr, $g:expr $(, $rest:expr)*) => {
11        |a| $f(a).and_then(|b| $g(b))$(.and_then(|x| $rest(x)))*
12    };
13}
14
15/// Chain two functions returning `Option`.
16pub fn chain_opt<A, B, C>(
17    f: impl Fn(A) -> Option<B>,
18    g: impl Fn(B) -> Option<C>,
19) -> impl Fn(A) -> Option<C> {
20    move |a| f(a).and_then(|b| g(b))
21}
22
23// Result version (like Swift's throws -> Optional)
24pub fn chain_result<A, B, C, E>(
25    f: impl Fn(A) -> Result<B, E>,
26    g: impl Fn(B) -> Result<C, E>,
27) -> impl Fn(A) -> Result<C, E> {
28    move |a| f(a).and_then(|b| g(b))
29}
30
31// Vec version (like Swift's arrays)
32pub fn chain_vec<A, B, C>(
33    f: impl Fn(A) -> Vec<B>,
34    g: impl Fn(B) -> Vec<C>,
35) -> impl Fn(A) -> Vec<C> {
36    move |a| f(a).into_iter().flat_map(|b| g(b)).collect()
37}
38
39#[cfg(test)]
40mod tests {
41    use super::*;
42
43    fn str_to_int(s: &str) -> Option<i32> {
44        s.parse().ok()
45    }
46
47    fn double(n: i32) -> Option<i32> {
48        Some(n * 2)
49    }
50
51    fn to_string(n: i32) -> Option<String> {
52        Some(format!("Number: {}", n))
53    }
54
55    #[test]
56    fn test_chain_opt_success() {
57        let f = chain_opt(str_to_int, double);
58        assert_eq!(f("21"), Some(42));
59    }
60
61    #[test]
62    fn test_chain_opt_failure() {
63        let f = chain_opt(str_to_int, double);
64        assert_eq!(f("not-a-number"), None);
65    }
66
67    #[test]
68    fn test_chain_vec_basic() {
69        let f = chain_vec(|n| vec![n, n + 1], |x| vec![x * 2]);
70        assert_eq!(f(3), vec![6, 8]);
71    }
72
73    #[test]
74    fn test_chain_vec_empty() {
75        let f = chain_vec(|_: i32| Vec::<i32>::new(), |x| vec![x * 2]);
76        assert_eq!(f(3), vec![]);
77    }
78
79    #[test]
80    fn test_chain_result_success() {
81        let f = chain_result(|s: &str| s.parse::<i32>(), |n| Ok(n * 10));
82        assert_eq!(f("5").unwrap(), 50);
83    }
84
85    #[test]
86    fn test_chain_result_failure_first() {
87        let f = chain_result(|s: &str| s.parse::<i32>(), |n| Ok(n * 10));
88        assert!(f("foo").is_err());
89    }
90
91    #[test]
92    fn test_chain_macro_option_success() {
93        let f = chain!(str_to_int, double, to_string);
94        assert_eq!(f("7"), Some("Number: 14".to_string()));
95    }
96
97    #[test]
98    fn test_chain_macro_option_failure() {
99        let f = chain!(str_to_int, double, to_string);
100        assert_eq!(f("oops"), None);
101    }
102}