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}