Skip to main content

karpal_core/
macros.rs

1// Copyright (C) 2026 Industrial Algebra
2// SPDX-License-Identifier: Apache-2.0
3
4/// Monadic do-notation sugar.
5///
6/// Desugars sequential `x = expr` bindings into nested `Chain::chain` calls.
7///
8/// # Example
9/// ```ignore
10/// do_! { OptionF;
11///     x = Some(1);
12///     y = Some(x + 1);
13///     OptionF::pure(x + y)
14/// }
15/// ```
16#[macro_export]
17macro_rules! do_ {
18    // Terminal: just an expression (no more bindings)
19    ($F:ty; $e:expr) => {
20        $e
21    };
22    // Binding: x = expr; rest...
23    ($F:ty; $x:ident = $e:expr; $($rest:tt)+) => {
24        <$F as $crate::chain::Chain>::chain($e, |$x| {
25            $crate::do_!($F; $($rest)+)
26        })
27    };
28}
29
30/// Applicative do-notation sugar.
31///
32/// Collects independent bindings and combines them with `ap`/`fmap`.
33/// Supports 1–4 bindings followed by `yield expr`.
34///
35/// # Example
36/// ```ignore
37/// ado_! { OptionF;
38///     x = Some(1);
39///     y = Some(2);
40///     yield x + y
41/// }
42/// ```
43#[macro_export]
44macro_rules! ado_ {
45    // 1 binding
46    ($F:ty; $x:ident = $e:expr; yield $body:expr) => {
47        <$F as $crate::functor::Functor>::fmap($e, |$x| $body)
48    };
49    // 2 bindings
50    ($F:ty; $x1:ident = $e1:expr; $x2:ident = $e2:expr; yield $body:expr) => {
51        <$F as $crate::apply::Apply>::ap(
52            <$F as $crate::functor::Functor>::fmap($e1, |$x1| move |$x2| $body),
53            $e2,
54        )
55    };
56    // 3 bindings
57    ($F:ty;
58        $x1:ident = $e1:expr;
59        $x2:ident = $e2:expr;
60        $x3:ident = $e3:expr;
61        yield $body:expr
62    ) => {
63        <$F as $crate::apply::Apply>::ap(
64            <$F as $crate::apply::Apply>::ap(
65                <$F as $crate::functor::Functor>::fmap($e1, |$x1| move |$x2| move |$x3| $body),
66                $e2,
67            ),
68            $e3,
69        )
70    };
71    // 4 bindings
72    ($F:ty;
73        $x1:ident = $e1:expr;
74        $x2:ident = $e2:expr;
75        $x3:ident = $e3:expr;
76        $x4:ident = $e4:expr;
77        yield $body:expr
78    ) => {
79        <$F as $crate::apply::Apply>::ap(
80            <$F as $crate::apply::Apply>::ap(
81                <$F as $crate::apply::Apply>::ap(
82                    <$F as $crate::functor::Functor>::fmap($e1, |$x1| {
83                        move |$x2| move |$x3| move |$x4| $body
84                    }),
85                    $e2,
86                ),
87                $e3,
88            ),
89            $e4,
90        )
91    };
92}
93
94#[cfg(test)]
95mod tests {
96    use crate::applicative::Applicative;
97    use crate::hkt::OptionF;
98    #[cfg(any(feature = "std", feature = "alloc"))]
99    use crate::hkt::VecF;
100
101    #[test]
102    fn do_option_some() {
103        let result = do_! { OptionF;
104            x = Some(1);
105            y = Some(x + 1);
106            OptionF::pure(x + y)
107        };
108        assert_eq!(result, Some(3));
109    }
110
111    #[test]
112    fn do_option_none() {
113        let result: Option<i32> = do_! { OptionF;
114            x = Some(1);
115            _y = None::<i32>;
116            OptionF::pure(x)
117        };
118        assert_eq!(result, None);
119    }
120
121    #[test]
122    fn do_option_single() {
123        let result = do_! { OptionF;
124            Some(42)
125        };
126        assert_eq!(result, Some(42));
127    }
128
129    #[test]
130    fn do_vec() {
131        let result = do_! { VecF;
132            x = vec![1, 2];
133            y = vec![10, 20];
134            VecF::pure(x + y)
135        };
136        assert_eq!(result, vec![11, 21, 12, 22]);
137    }
138
139    #[test]
140    fn ado_option_1() {
141        let result = ado_! { OptionF;
142            x = Some(5);
143            yield x * 2
144        };
145        assert_eq!(result, Some(10));
146    }
147
148    #[test]
149    fn ado_option_2() {
150        let result = ado_! { OptionF;
151            x = Some(1);
152            y = Some(2);
153            yield x + y
154        };
155        assert_eq!(result, Some(3));
156    }
157
158    #[test]
159    fn ado_option_2_none() {
160        let result = ado_! { OptionF;
161            x = Some(1);
162            y = None::<i32>;
163            yield x + y
164        };
165        assert_eq!(result, None);
166    }
167
168    #[test]
169    fn ado_option_3() {
170        let result = ado_! { OptionF;
171            x = Some(1);
172            y = Some(2);
173            z = Some(3);
174            yield x + y + z
175        };
176        assert_eq!(result, Some(6));
177    }
178
179    #[test]
180    fn ado_option_4() {
181        let result = ado_! { OptionF;
182            a = Some(1);
183            b = Some(2);
184            c = Some(3);
185            d = Some(4);
186            yield a + b + c + d
187        };
188        assert_eq!(result, Some(10));
189    }
190
191    #[test]
192    fn ado_vec_2() {
193        let result = ado_! { VecF;
194            x = vec![1, 2];
195            y = vec![10, 20];
196            yield x + y
197        };
198        assert_eq!(result, vec![11, 21, 12, 22]);
199    }
200}