overture_core/compose.rs
1//! Backward composition of functions.
2//!
3//! This module provides functions for composing functions in a backward manner,
4//! where the output of one function becomes the input of the next.
5//! This is commonly seen in operator form as `<<<` in functional programming languages.
6
7/// Backward composition of two functions.
8///
9/// # Arguments
10/// * `f` - A function that takes a value in `B` and returns a value in `C`
11/// * `g` - A function that takes a value in `A` and returns a value in `B`
12///
13/// # Returns
14/// A new function that takes a value in `A` and returns a value in `C`
15///
16/// # Example
17/// ```
18/// use overture_core::compose::compose;
19///
20/// let add_one = |x: i32| x + 1;
21/// let multiply_by_two = |x: i32| x * 2;
22/// let composed = compose(multiply_by_two, add_one);
23///
24/// assert_eq!(composed(5), 12); // (5 + 1) * 2 = 12
25/// ```
26pub fn compose<A, B, C, F, G>(f: F, g: G) -> impl Fn(A) -> C
27where
28 F: Fn(B) -> C,
29 G: Fn(A) -> B,
30{
31 move |a: A| f(g(a))
32}
33
34/// Backward composition of three functions.
35///
36/// # Arguments
37/// * `f` - A function that takes a value in `C` and returns a value in `D`
38/// * `g` - A function that takes a value in `B` and returns a value in `C`
39/// * `h` - A function that takes a value in `A` and returns a value in `B`
40///
41/// # Returns
42/// A new function that takes a value in `A` and returns a value in `D`
43pub fn compose3<A, B, C, D, F, G, H>(f: F, g: G, h: H) -> impl Fn(A) -> D
44where
45 F: Fn(C) -> D,
46 G: Fn(B) -> C,
47 H: Fn(A) -> B,
48{
49 move |a: A| f(g(h(a)))
50}
51
52/// Backward composition of four functions.
53///
54/// # Arguments
55/// * `f` - A function that takes a value in `D` and returns a value in `E`
56/// * `g` - A function that takes a value in `C` and returns a value in `D`
57/// * `h` - A function that takes a value in `B` and returns a value in `C`
58/// * `i` - A function that takes a value in `A` and returns a value in `B`
59///
60/// # Returns
61/// A new function that takes a value in `A` and returns a value in `E`
62pub fn compose4<A, B, C, D, E, F, G, H, I>(f: F, g: G, h: H, i: I) -> impl Fn(A) -> E
63where
64 F: Fn(D) -> E,
65 G: Fn(C) -> D,
66 H: Fn(B) -> C,
67 I: Fn(A) -> B,
68{
69 move |a: A| f(g(h(i(a))))
70}
71
72/// Backward composition of five functions.
73///
74/// # Arguments
75/// * `f` - A function that takes a value in `E` and returns a value in `F`
76/// * `g` - A function that takes a value in `D` and returns a value in `E`
77/// * `h` - A function that takes a value in `C` and returns a value in `D`
78/// * `i` - A function that takes a value in `B` and returns a value in `C`
79/// * `j` - A function that takes a value in `A` and returns a value in `B`
80///
81/// # Returns
82/// A new function that takes a value in `A` and returns a value in `F`
83pub fn compose5<A, B, C, D, E, F, FuncF, FuncG, FuncH, FuncI, FuncJ>(
84 f: FuncF,
85 g: FuncG,
86 h: FuncH,
87 i: FuncI,
88 j: FuncJ,
89) -> impl Fn(A) -> F
90where
91 FuncF: Fn(E) -> F,
92 FuncG: Fn(D) -> E,
93 FuncH: Fn(C) -> D,
94 FuncI: Fn(B) -> C,
95 FuncJ: Fn(A) -> B,
96{
97 move |a: A| f(g(h(i(j(a)))))
98}
99
100/// Backward composition of six functions.
101///
102/// # Arguments
103/// * `f` - A function that takes a value in `F` and returns a value in `G`
104/// * `g` - A function that takes a value in `E` and returns a value in `F`
105/// * `h` - A function that takes a value in `D` and returns a value in `E`
106/// * `i` - A function that takes a value in `C` and returns a value in `D`
107/// * `j` - A function that takes a value in `B` and returns a value in `C`
108/// * `k` - A function that takes a value in `A` and returns a value in `B`
109///
110/// # Returns
111/// A new function that takes a value in `A` and returns a value in `G`
112pub fn compose6<A, B, C, D, E, F, G, FuncF, FuncG, FuncH, FuncI, FuncJ, FuncK>(
113 f: FuncF,
114 g: FuncG,
115 h: FuncH,
116 i: FuncI,
117 j: FuncJ,
118 k: FuncK,
119) -> impl Fn(A) -> G
120where
121 FuncF: Fn(F) -> G,
122 FuncG: Fn(E) -> F,
123 FuncH: Fn(D) -> E,
124 FuncI: Fn(C) -> D,
125 FuncJ: Fn(B) -> C,
126 FuncK: Fn(A) -> B,
127{
128 move |a: A| f(g(h(i(j(k(a))))))
129}
130
131/// Backward composition of two throwing functions.
132///
133/// # Arguments
134/// * `f` - A function that takes a value in `B` and returns a `Result<C, E>`
135/// * `g` - A function that takes a value in `A` and returns a `Result<B, E>`
136///
137/// # Returns
138/// A new function that takes a value in `A` and returns a `Result<C, E>`
139///
140/// # Example
141/// ```
142/// use overture_core::compose::compose_throwing;
143///
144/// let parse_int = |s: &str| s.parse::<i32>().map_err(|_| "Parse error");
145/// let add_one = |x: i32| Ok(x + 1);
146/// let composed = compose_throwing(add_one, parse_int);
147///
148/// assert_eq!(composed("5"), Ok(6));
149/// assert_eq!(composed("invalid"), Err("Parse error"));
150/// ```
151pub fn compose_throwing<A, B, C, E, F, G>(f: F, g: G) -> impl Fn(A) -> Result<C, E>
152where
153 F: Fn(B) -> Result<C, E> + Clone,
154 G: Fn(A) -> Result<B, E> + Clone,
155{
156 move |a: A| g.clone()(a).and_then(f.clone())
157}
158
159/// Backward composition of three throwing functions.
160///
161/// # Arguments
162/// * `f` - A function that takes a value in `C` and returns a `Result<D, E>`
163/// * `g` - A function that takes a value in `B` and returns a `Result<C, E>`
164/// * `h` - A function that takes a value in `A` and returns a `Result<B, E>`
165///
166/// # Returns
167/// A new function that takes a value in `A` and returns a `Result<D, E>`
168pub fn compose3_throwing<A, B, C, D, E, F, G, H>(
169 f: F,
170 g: G,
171 h: H,
172) -> impl Fn(A) -> Result<D, E>
173where
174 F: Fn(C) -> Result<D, E> + Clone,
175 G: Fn(B) -> Result<C, E> + Clone,
176 H: Fn(A) -> Result<B, E> + Clone,
177{
178 move |a: A| h.clone()(a).and_then(g.clone()).and_then(f.clone())
179}
180
181/// Backward composition of four throwing functions.
182///
183/// # Arguments
184/// * `f` - A function that takes a value in `D` and returns a `Result<E, E>`
185/// * `g` - A function that takes a value in `C` and returns a `Result<D, E>`
186/// * `h` - A function that takes a value in `B` and returns a `Result<C, E>`
187/// * `i` - A function that takes a value in `A` and returns a `Result<B, E>`
188///
189/// # Returns
190/// A new function that takes a value in `A` and returns a `Result<E, E>`
191pub fn compose4_throwing<A, B, C, D, E, F, G, H, I>(
192 f: F,
193 g: G,
194 h: H,
195 i: I,
196) -> impl Fn(A) -> Result<E, E>
197where
198 F: Fn(D) -> Result<E, E> + Clone,
199 G: Fn(C) -> Result<D, E> + Clone,
200 H: Fn(B) -> Result<C, E> + Clone,
201 I: Fn(A) -> Result<B, E> + Clone,
202{
203 move |a: A| i.clone()(a).and_then(h.clone()).and_then(g.clone()).and_then(f.clone())
204}
205
206/// Backward composition of five throwing functions.
207///
208/// # Arguments
209/// * `f` - A function that takes a value in `E` and returns a `Result<F, E>`
210/// * `g` - A function that takes a value in `D` and returns a `Result<E, E>`
211/// * `h` - A function that takes a value in `C` and returns a `Result<D, E>`
212/// * `i` - A function that takes a value in `B` and returns a `Result<C, E>`
213/// * `j` - A function that takes a value in `A` and returns a `Result<B, E>`
214///
215/// # Returns
216/// A new function that takes a value in `A` and returns a `Result<F, E>`
217pub fn compose5_throwing<A, B, C, D, E, F, FuncF, FuncG, FuncH, FuncI, FuncJ>(
218 f: FuncF,
219 g: FuncG,
220 h: FuncH,
221 i: FuncI,
222 j: FuncJ,
223) -> impl Fn(A) -> Result<F, E>
224where
225 FuncF: Fn(E) -> Result<F, E> + Clone,
226 FuncG: Fn(D) -> Result<E, E> + Clone,
227 FuncH: Fn(C) -> Result<D, E> + Clone,
228 FuncI: Fn(B) -> Result<C, E> + Clone,
229 FuncJ: Fn(A) -> Result<B, E> + Clone,
230{
231 move |a: A| j.clone()(a).and_then(i.clone()).and_then(h.clone()).and_then(g.clone()).and_then(f.clone())
232}
233
234/// Backward composition of six throwing functions.
235///
236/// # Arguments
237/// * `f` - A function that takes a value in `F` and returns a `Result<G, E>`
238/// * `g` - A function that takes a value in `E` and returns a `Result<F, E>`
239/// * `h` - A function that takes a value in `D` and returns a `Result<E, E>`
240/// * `i` - A function that takes a value in `C` and returns a `Result<D, E>`
241/// * `j` - A function that takes a value in `B` and returns a `Result<C, E>`
242/// * `k` - A function that takes a value in `A` and returns a `Result<B, E>`
243///
244/// # Returns
245/// A new function that takes a value in `A` and returns a `Result<G, E>`
246pub fn compose6_throwing<A, B, C, D, E, F, G, FuncF, FuncG, FuncH, FuncI, FuncJ, FuncK>(
247 f: FuncF,
248 g: FuncG,
249 h: FuncH,
250 i: FuncI,
251 j: FuncJ,
252 k: FuncK,
253) -> impl Fn(A) -> Result<G, E>
254where
255 FuncF: Fn(F) -> Result<G, E> + Clone,
256 FuncG: Fn(E) -> Result<F, E> + Clone,
257 FuncH: Fn(D) -> Result<E, E> + Clone,
258 FuncI: Fn(C) -> Result<D, E> + Clone,
259 FuncJ: Fn(B) -> Result<C, E> + Clone,
260 FuncK: Fn(A) -> Result<B, E> + Clone,
261{
262 move |a: A| k.clone()(a).and_then(j.clone()).and_then(i.clone()).and_then(h.clone()).and_then(g.clone()).and_then(f.clone())
263}
264
265#[cfg(test)]
266mod tests {
267 use super::*;
268
269 #[test]
270 fn test_compose_basic() {
271 let add_one = |x: i32| x + 1;
272 let multiply_by_two = |x: i32| x * 2;
273 let composed = compose(multiply_by_two, add_one);
274
275 assert_eq!(composed(5), 12); // (5 + 1) * 2 = 12
276 }
277
278 #[test]
279 fn test_compose3() {
280 let add_one = |x: i32| x + 1;
281 let multiply_by_two = |x: i32| x * 2;
282 let subtract_three = |x: i32| x - 3;
283 let composed = compose3(subtract_three, multiply_by_two, add_one);
284
285 assert_eq!(composed(5), 9); // ((5 + 1) * 2) - 3 = 9
286 }
287
288 #[test]
289 fn test_compose_throwing() {
290 let parse_int = |s: &str| s.parse::<i32>().map_err(|_| "Parse error");
291 let add_one = |x: i32| Ok(x + 1);
292 let composed = compose_throwing(add_one, parse_int);
293
294 assert_eq!(composed("5"), Ok(6));
295 assert_eq!(composed("invalid"), Err("Parse error"));
296 }
297
298 #[test]
299 fn test_compose3_throwing() {
300 let parse_int = |s: &str| s.parse::<i32>().map_err(|_| "Parse error");
301 let add_one = |x: i32| Ok(x + 1);
302 let multiply_by_two = |x: i32| Ok(x * 2);
303 let composed = compose3_throwing(multiply_by_two, add_one, parse_int);
304
305 assert_eq!(composed("5"), Ok(12)); // ((5 + 1) * 2) = 12
306 assert_eq!(composed("invalid"), Err("Parse error"));
307 }
308}