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
40#[cfg(test)]
41mod tests {
42    use super::*;
43
44    fn str_to_int(s: &str) -> Option<i32> {
45        s.parse().ok()
46    }
47
48    fn double(n: i32) -> Option<i32> {
49        Some(n * 2)
50    }
51
52    fn to_string(n: i32) -> Option<String> {
53        Some(format!("Number: {}", n))
54    }
55
56    #[test]
57    fn test_chain_opt_success() {
58        let f = chain_opt(str_to_int, double);
59        assert_eq!(f("21"), Some(42));
60    }
61
62    #[test]
63    fn test_chain_opt_failure() {
64        let f = chain_opt(str_to_int, double);
65        assert_eq!(f("not-a-number"), None);
66    }
67
68    #[test]
69    fn test_chain_vec_basic() {
70        let f = chain_vec(|n| vec![n, n + 1], |x| vec![x * 2]);
71        assert_eq!(f(3), vec![6, 8]);
72    }
73
74    #[test]
75    fn test_chain_vec_empty() {
76        let f = chain_vec(|_: i32| Vec::<i32>::new(), |x| vec![x * 2]);
77        assert_eq!(f(3), vec![]);
78    }
79
80    #[test]
81    fn test_chain_result_success() {
82        let f = chain_result(|s: &str| s.parse::<i32>(), |n| Ok(n * 10));
83        assert_eq!(f("5").unwrap(), 50);
84    }
85
86    #[test]
87    fn test_chain_result_failure_first() {
88        let f = chain_result(|s: &str| s.parse::<i32>(), |n| Ok(n * 10));
89        assert!(f("foo").is_err());
90    }
91
92    #[test]
93    fn test_chain_macro_option_success() {
94        let f = chain!(str_to_int, double, to_string);
95        assert_eq!(f("7"), Some("Number: 14".to_string()));
96    }
97
98    #[test]
99    fn test_chain_macro_option_failure() {
100        let f = chain!(str_to_int, double, to_string);
101        assert_eq!(f("oops"), None);
102    }
103}