composing/
lib.rs

1/*! Tools to compose functions.
2
3This library exports two macros, [`compose_expr`] and [`compose_fn`],
4which allow to easily compose expressions and functions respectively.
5They both support right-to-left and left-to-right composition:
6
7- right-to-left composition is achieved by separating the arguments with `,`;
8- left-to-right composition is achieved by separating the arguments with `=>`.
9
10# Examples
11
12```
13use composing::*;
14
15fn plus_one(x: i32) -> i32 { x + 1 }
16fn times_two(x: i32) -> i32 { x * 2 }
17fn to_string(x: i32) -> String { x.to_string() }
18
19let composition = compose_fn!(to_string, plus_one, times_two);
20assert_eq!(composition(17), "35");
21
22let composition = compose_fn!(times_two => plus_one => to_string);
23assert_eq!(composition(17), "35");
24```
25*/
26
27#![cfg_attr(not(feature = "std"), no_std)]
28#![cfg_attr(docsrs, feature(doc_auto_cfg))]
29
30/// Macro to compose expressions.
31///
32/// # Usage
33///
34/// The macro supports both right-to-left and left-to-right composition:
35///
36/// - right-to-left composition is achieved by giving the macro a comma-separated
37/// list of expressions;
38/// - left-to-right composition is achieved by giving the macro a list of expressions
39/// separated by `=>`.
40///
41/// For instance,
42/// - `compose_expr!(a, b, c, d)` expands to `a(b(c(d)))`;
43/// - `compose_expr!(a => b => c => d)` expands to `d(c(b(a)))`.
44///
45/// # Examples
46///
47/// ## Right-to-left
48///
49/// ```
50/// # use composing::compose_expr;
51/// // equivalent to `|x| (x * 2) + 1`
52/// let composition = |x| compose_expr!(|x| x + 1, |x| x * 2, x);
53/// assert_eq!(composition(1), 3);
54/// ```
55///
56/// ## Left-to-right
57///
58/// ```
59/// # use composing::compose_expr;
60/// // equivalent to `|x| (x + 1) * 2`
61/// let composition = |x| compose_expr!(x => |x| x + 1 => |x| x * 2);
62/// assert_eq!(composition(1), 4);
63/// ```
64#[macro_export]
65macro_rules! compose_expr {
66    () => {};
67    ($expr:expr) => { $expr };
68
69    // right-to-left composition
70    ($head:expr, $($tail:expr),+) => { $head($crate::compose_expr!($($tail),+)) };
71
72    // left-to-right composition
73    ($first:expr => $second:expr) => { $second($first) };
74    ($first:expr => $second:expr => $($tail:expr)=>+) => {
75        $crate::compose_expr!($second($first) => $($tail)=>+)
76    }
77}
78
79/// Macro to compose functions.
80///
81/// # Usage
82///
83/// The macro supports both right-to-left and left-to-right composition:
84///
85/// - right-to-left composition is achieved by giving the macro a comma-separated
86/// list of functions;
87/// - left-to-right composition is achieved by giving the macro a list of functions
88/// separated by `=>`.
89///
90/// For instance,
91/// - `compose_fn!(a, b, c, d)` expands to `|x| a(b(c(d(x))))`;
92/// - `compose_fn!(a => b => c => d)` expands to `|x| d(c(b(a(x))))`.
93///
94/// # Examples
95///
96/// ## Right-to-left
97///
98/// ```
99/// # use composing::compose_fn;
100/// // equivalent to `|x| (x * 2) + 1`
101/// let composition = compose_fn!(|x| x + 1, |x| x * 2);
102/// assert_eq!(composition(1), 3);
103/// ```
104///
105/// ## Left-to-right
106///
107/// ```
108/// # use composing::compose_fn;
109/// // equivalent to `|x| (x + 1) * 2`
110/// let composition = compose_fn!(|x| x + 1 => |x| x * 2);
111/// assert_eq!(composition(1), 4);
112/// ```
113#[macro_export]
114macro_rules! compose_fn {
115    () => { |x| x };
116    ($($fns:expr),+) => { |x| $crate::compose_expr!($($fns),+, x) };
117    ($($fns:expr)=>+) => { |x| $crate::compose_expr!(x => $($fns)=>+) };
118}
119
120#[cfg(test)]
121mod tests {
122    use super::*;
123
124    #[test]
125    fn expr_right_to_left() {
126        let composition = |x| compose_expr!(x);
127        assert_eq!(composition(1), 1);
128
129        let composition = |x| compose_expr!(|x| x + 1, x);
130        assert_eq!(composition(1), 2);
131
132        let composition = |x| compose_expr!(|x| x + 1, |x| x * 2, x);
133        assert_eq!(composition(1), 3);
134
135        let composition = |x| compose_expr!(|x| x * 2, |x| x + 1, x);
136        assert_eq!(composition(1), 4);
137    }
138
139    #[test]
140    fn expr_left_to_right() {
141        let composition = |x| compose_expr!(x);
142        assert_eq!(composition(1), 1);
143
144        let composition = |x| compose_expr!(x => |x| x + 1);
145        assert_eq!(composition(1), 2);
146
147        let composition = |x| compose_expr!(x => |x| x * 2 => |x| x + 1);
148        assert_eq!(composition(1), 3);
149
150        let composition = |x| compose_expr!(x => |x| x + 1 => |x| x * 2);
151        assert_eq!(composition(1), 4);
152    }
153
154    #[test]
155    fn fn_right_to_left() {
156        let composition = compose_fn!();
157        assert_eq!(composition(1), 1);
158
159        let composition = compose_fn!(|x| x + 1);
160        assert_eq!(composition(1), 2);
161
162        let composition = compose_fn!(|x| x + 1, |x| x * 2);
163        assert_eq!(composition(1), 3);
164
165        let composition = compose_fn!(|x| x * 2, |x| x + 1);
166        assert_eq!(composition(1), 4);
167    }
168
169    #[test]
170    fn fn_left_to_right() {
171        let composition = compose_fn!();
172        assert_eq!(composition(1), 1);
173
174        let composition = compose_fn!(|x| x + 1);
175        assert_eq!(composition(1), 2);
176
177        let composition = compose_fn!( |x| x * 2 => |x| x + 1);
178        assert_eq!(composition(1), 3);
179
180        let composition = compose_fn!( |x| x + 1 => |x| x * 2);
181        assert_eq!(composition(1), 4);
182    }
183
184    #[test]
185    #[cfg(feature = "std")]
186    fn advanced() {
187        fn plus_one(x: i32) -> i32 {
188            x + 1
189        }
190        fn times_two(x: i32) -> i32 {
191            x * 2
192        }
193        fn to_string(x: i32) -> String {
194            x.to_string()
195        }
196
197        let composition = compose_fn!(to_string, plus_one, times_two);
198        assert_eq!(composition(17), "35");
199
200        let composition = compose_fn!(times_two => plus_one => to_string);
201        assert_eq!(composition(17), "35");
202    }
203}