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
7use std::rc::Rc;
8
9/// Backward composition of two functions.
10///
11/// # Arguments
12/// * `f` - A function that takes a value in `B` and returns a value in `C`
13/// * `g` - A function that takes a value in `A` and returns a value in `B`
14///
15/// # Returns
16/// A new function that takes a value in `A` and returns a value in `C`
17///
18/// # Example
19/// ```
20/// use overture_core::compose::compose;
21///
22/// let add_one = |x: i32| x + 1;
23/// let multiply_by_two = |x: i32| x * 2;
24/// let composed = compose(multiply_by_two, add_one);
25///
26/// assert_eq!(composed(5), 12); // (5 + 1) * 2 = 12
27/// ```
28pub fn compose<A, B, C, F, G>(f: F, g: G) -> impl Fn(A) -> C
29where
30 F: Fn(B) -> C,
31 G: Fn(A) -> B,
32{
33 move |a: A| f(g(a))
34}
35
36/// Backward composition of three functions.
37///
38/// # Arguments
39/// * `f` - A function that takes a value in `C` and returns a value in `D`
40/// * `g` - A function that takes a value in `B` and returns a value in `C`
41/// * `h` - A function that takes a value in `A` and returns a value in `B`
42///
43/// # Returns
44/// A new function that takes a value in `A` and returns a value in `D`
45pub fn compose3<A, B, C, D, F, G, H>(f: F, g: G, h: H) -> impl Fn(A) -> D
46where
47 F: Fn(C) -> D,
48 G: Fn(B) -> C,
49 H: Fn(A) -> B,
50{
51 move |a: A| f(g(h(a)))
52}
53
54/// Backward composition of four functions.
55///
56/// # Arguments
57/// * `f` - A function that takes a value in `D` and returns a value in `E`
58/// * `g` - A function that takes a value in `C` and returns a value in `D`
59/// * `h` - A function that takes a value in `B` and returns a value in `C`
60/// * `i` - A function that takes a value in `A` and returns a value in `B`
61///
62/// # Returns
63/// A new function that takes a value in `A` and returns a value in `E`
64pub fn compose4<A, B, C, D, E, F, G, H, I>(f: F, g: G, h: H, i: I) -> impl Fn(A) -> E
65where
66 F: Fn(D) -> E,
67 G: Fn(C) -> D,
68 H: Fn(B) -> C,
69 I: Fn(A) -> B,
70{
71 move |a: A| f(g(h(i(a))))
72}
73
74/// Backward composition of five functions.
75///
76/// # Arguments
77/// * `f` - A function that takes a value in `E` and returns a value in `F`
78/// * `g` - A function that takes a value in `D` and returns a value in `E`
79/// * `h` - A function that takes a value in `C` and returns a value in `D`
80/// * `i` - A function that takes a value in `B` and returns a value in `C`
81/// * `j` - A function that takes a value in `A` and returns a value in `B`
82///
83/// # Returns
84/// A new function that takes a value in `A` and returns a value in `F`
85pub fn compose5<A, B, C, D, E, F, FuncF, FuncG, FuncH, FuncI, FuncJ>(
86 f: FuncF,
87 g: FuncG,
88 h: FuncH,
89 i: FuncI,
90 j: FuncJ,
91) -> impl Fn(A) -> F
92where
93 FuncF: Fn(E) -> F,
94 FuncG: Fn(D) -> E,
95 FuncH: Fn(C) -> D,
96 FuncI: Fn(B) -> C,
97 FuncJ: Fn(A) -> B,
98{
99 move |a: A| f(g(h(i(j(a)))))
100}
101
102/// Backward composition of six functions.
103///
104/// # Arguments
105/// * `f` - A function that takes a value in `F` and returns a value in `G`
106/// * `g` - A function that takes a value in `E` and returns a value in `F`
107/// * `h` - A function that takes a value in `D` and returns a value in `E`
108/// * `i` - A function that takes a value in `C` and returns a value in `D`
109/// * `j` - A function that takes a value in `B` and returns a value in `C`
110/// * `k` - A function that takes a value in `A` and returns a value in `B`
111///
112/// # Returns
113/// A new function that takes a value in `A` and returns a value in `G`
114pub fn compose6<A, B, C, D, E, F, G, FuncF, FuncG, FuncH, FuncI, FuncJ, FuncK>(
115 f: FuncF,
116 g: FuncG,
117 h: FuncH,
118 i: FuncI,
119 j: FuncJ,
120 k: FuncK,
121) -> impl Fn(A) -> G
122where
123 FuncF: Fn(F) -> G,
124 FuncG: Fn(E) -> F,
125 FuncH: Fn(D) -> E,
126 FuncI: Fn(C) -> D,
127 FuncJ: Fn(B) -> C,
128 FuncK: Fn(A) -> B,
129{
130 move |a: A| f(g(h(i(j(k(a))))))
131}
132
133// /// Backward composition of two throwing functions.
134// ///
135// /// # Arguments
136// /// * `f` - A function that takes a value in `B` and returns a `Result<C, E>`
137// /// * `g` - A function that takes a value in `A` and returns a `Result<B, E>`
138// ///
139// /// # Returns
140// /// A new function that takes a value in `A` and returns a `Result<C, E>`
141// ///
142// /// # Example
143// /// ```
144// /// use overture_core::compose::compose_throwing;
145// ///
146// /// let parse_int = |s: &str| s.parse::<i32>().map_err(|_| "Parse error");
147// /// let add_one = |x: i32| Ok(x + 1);
148// /// let composed = compose_throwing(add_one, parse_int);
149// ///
150// /// assert_eq!(composed("5"), Ok(6));
151// /// assert_eq!(composed("invalid"), Err("Parse error"));
152// /// ```
153// pub fn compose_throwing<A, B, C, E, F, G>(f: F, g: G) -> impl Fn(A) -> Result<C, E>
154// where
155// F: Fn(B) -> Result<C, E> + Clone,
156// G: Fn(A) -> Result<B, E> + Clone,
157// {
158// move |a: A| g.clone()(a).and_then(f.clone())
159// }
160
161// /// Backward composition of three throwing functions.
162// ///
163// /// # Arguments
164// /// * `f` - A function that takes a value in `C` and returns a `Result<D, E>`
165// /// * `g` - A function that takes a value in `B` and returns a `Result<C, E>`
166// /// * `h` - A function that takes a value in `A` and returns a `Result<B, E>`
167// ///
168// /// # Returns
169// /// A new function that takes a value in `A` and returns a `Result<D, E>`
170// pub fn compose3_throwing<A, B, C, D, E, F, G, H>(
171// f: F,
172// g: G,
173// h: H,
174// ) -> impl Fn(A) -> Result<D, E>
175// where
176// F: Fn(C) -> Result<D, E> + Clone,
177// G: Fn(B) -> Result<C, E> + Clone,
178// H: Fn(A) -> Result<B, E> + Clone,
179// {
180// move |a: A| h.clone()(a).and_then(g.clone()).and_then(f.clone())
181// }
182
183// /// Backward composition of four throwing functions.
184// ///
185// /// # Arguments
186// /// * `f` - A function that takes a value in `D` and returns a `Result<E, E>`
187// /// * `g` - A function that takes a value in `C` and returns a `Result<D, E>`
188// /// * `h` - A function that takes a value in `B` and returns a `Result<C, E>`
189// /// * `i` - A function that takes a value in `A` and returns a `Result<B, E>`
190// ///
191// /// # Returns
192// /// A new function that takes a value in `A` and returns a `Result<E, E>`
193// pub fn compose4_throwing<A, B, C, D, E, F, G, H, I>(
194// f: F,
195// g: G,
196// h: H,
197// i: I,
198// ) -> impl Fn(A) -> Result<E, E>
199// where
200// F: Fn(D) -> Result<E, E> + Clone,
201// G: Fn(C) -> Result<D, E> + Clone,
202// H: Fn(B) -> Result<C, E> + Clone,
203// I: Fn(A) -> Result<B, E> + Clone,
204// {
205// move |a: A| i.clone()(a).and_then(h.clone()).and_then(g.clone()).and_then(f.clone())
206// }
207
208// /// Backward composition of five throwing functions.
209// ///
210// /// # Arguments
211// /// * `f` - A function that takes a value in `E` and returns a `Result<F, E>`
212// /// * `g` - A function that takes a value in `D` and returns a `Result<E, E>`
213// /// * `h` - A function that takes a value in `C` and returns a `Result<D, E>`
214// /// * `i` - A function that takes a value in `B` and returns a `Result<C, E>`
215// /// * `j` - A function that takes a value in `A` and returns a `Result<B, E>`
216// ///
217// /// # Returns
218// /// A new function that takes a value in `A` and returns a `Result<F, E>`
219// pub fn compose5_throwing<A, B, C, D, E, F, FuncF, FuncG, FuncH, FuncI, FuncJ>(
220// f: FuncF,
221// g: FuncG,
222// h: FuncH,
223// i: FuncI,
224// j: FuncJ,
225// ) -> impl Fn(A) -> Result<F, E>
226// where
227// FuncF: Fn(E) -> Result<F, E> + Clone,
228// FuncG: Fn(D) -> Result<E, E> + Clone,
229// FuncH: Fn(C) -> Result<D, E> + Clone,
230// FuncI: Fn(B) -> Result<C, E> + Clone,
231// FuncJ: Fn(A) -> Result<B, E> + Clone,
232// {
233// move |a: A| j.clone()(a).and_then(i.clone()).and_then(h.clone()).and_then(g.clone()).and_then(f.clone())
234// }
235
236// /// Backward composition of six throwing functions.
237// ///
238// /// # Arguments
239// /// * `f` - A function that takes a value in `F` and returns a `Result<G, E>`
240// /// * `g` - A function that takes a value in `E` and returns a `Result<F, E>`
241// /// * `h` - A function that takes a value in `D` and returns a `Result<E, E>`
242// /// * `i` - A function that takes a value in `C` and returns a `Result<D, E>`
243// /// * `j` - A function that takes a value in `B` and returns a `Result<C, E>`
244// /// * `k` - A function that takes a value in `A` and returns a `Result<B, E>`
245// ///
246// /// # Returns
247// /// A new function that takes a value in `A` and returns a `Result<G, E>`
248// pub fn compose6_throwing<A, B, C, D, E, F, G, FuncF, FuncG, FuncH, FuncI, FuncJ, FuncK>(
249// f: FuncF,
250// g: FuncG,
251// h: FuncH,
252// i: FuncI,
253// j: FuncJ,
254// k: FuncK,
255// ) -> impl Fn(A) -> Result<G, E>
256// where
257// FuncF: Fn(F) -> Result<G, E> + Clone,
258// FuncG: Fn(E) -> Result<F, E> + Clone,
259// FuncH: Fn(D) -> Result<E, E> + Clone,
260// FuncI: Fn(C) -> Result<D, E> + Clone,
261// FuncJ: Fn(B) -> Result<C, E> + Clone,
262// FuncK: Fn(A) -> Result<B, E> + Clone,
263// {
264// 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())
265// }
266
267/// Backward composition of two throwing functions with shallow cloning optimization.
268///
269/// Uses `Rc` to enable shallow cloning of functions, reducing memory overhead
270/// when the same function is used multiple times in composed pipelines.
271///
272/// # Arguments
273/// * `f` - A function that takes a value in `B` and returns a `Result<C, E>`
274/// * `g` - A function that takes a value in `A` and returns a `Result<B, E>`
275///
276/// # Returns
277/// A new function that takes a value in `A` and returns a `Result<C, E>`
278///
279/// # Example
280/// ```
281/// use overture_core::compose_rs::compose_throwing;
282///
283/// let parse_int = |s: &str| s.parse::<i32>().map_err(|_| "Parse error");
284/// let add_one = |x: i32| Ok(x + 1);
285/// let composed = compose_throwing(add_one, parse_int);
286///
287/// assert_eq!(composed("5"), Ok(6));
288/// assert_eq!(composed("invalid"), Err("Parse error"));
289/// ```
290pub fn compose_throwing<A, B, C, E, F, G>(f: F, g: G) -> impl Fn(A) -> Result<C, E>
291where
292 F: Fn(B) -> Result<C, E> + 'static,
293 G: Fn(A) -> Result<B, E> + 'static,
294{
295 let f_rc = Rc::new(f);
296 let g_rc = Rc::new(g);
297 move |a: A| {
298 let f_ref = Rc::clone(&f_rc);
299 let g_ref = Rc::clone(&g_rc);
300 g_ref(a).and_then(|b| f_ref(b))
301 }
302}
303
304/// Backward composition of three throwing functions with shallow cloning optimization.
305///
306/// # Arguments
307/// * `f` - A function that takes a value in `C` and returns a `Result<D, E>`
308/// * `g` - A function that takes a value in `B` and returns a `Result<C, E>`
309/// * `h` - A function that takes a value in `A` and returns a `Result<B, E>`
310///
311/// # Returns
312/// A new function that takes a value in `A` and returns a `Result<D, E>`
313pub fn compose3_throwing<A, B, C, D, E, F, G, H>(f: F, g: G, h: H) -> impl Fn(A) -> Result<D, E>
314where
315 F: Fn(C) -> Result<D, E> + 'static,
316 G: Fn(B) -> Result<C, E> + 'static,
317 H: Fn(A) -> Result<B, E> + 'static,
318{
319 let f_rc = Rc::new(f);
320 let g_rc = Rc::new(g);
321 let h_rc = Rc::new(h);
322 move |a: A| {
323 let f_ref = Rc::clone(&f_rc);
324 let g_ref = Rc::clone(&g_rc);
325 let h_ref = Rc::clone(&h_rc);
326 h_ref(a).and_then(|b| g_ref(b)).and_then(|c| f_ref(c))
327 }
328}
329
330/// Backward composition of four throwing functions with shallow cloning optimization.
331///
332/// # Arguments
333/// * `f` - A function that takes a value in `D` and returns a `Result<E, Err>`
334/// * `g` - A function that takes a value in `C` and returns a `Result<D, Err>`
335/// * `h` - A function that takes a value in `B` and returns a `Result<C, Err>`
336/// * `i` - A function that takes a value in `A` and returns a `Result<B, Err>`
337///
338/// # Returns
339/// A new function that takes a value in `A` and returns a `Result<E, Err>`
340pub fn compose4_throwing<A, B, C, D, E, Err, F, G, H, I>(
341 f: F,
342 g: G,
343 h: H,
344 i: I,
345) -> impl Fn(A) -> Result<E, Err>
346where
347 F: Fn(D) -> Result<E, Err> + 'static,
348 G: Fn(C) -> Result<D, Err> + 'static,
349 H: Fn(B) -> Result<C, Err> + 'static,
350 I: Fn(A) -> Result<B, Err> + 'static,
351{
352 let f_rc = Rc::new(f);
353 let g_rc = Rc::new(g);
354 let h_rc = Rc::new(h);
355 let i_rc = Rc::new(i);
356 move |a: A| {
357 let f_ref = Rc::clone(&f_rc);
358 let g_ref = Rc::clone(&g_rc);
359 let h_ref = Rc::clone(&h_rc);
360 let i_ref = Rc::clone(&i_rc);
361 i_ref(a)
362 .and_then(|b| h_ref(b))
363 .and_then(|c| g_ref(c))
364 .and_then(|d| f_ref(d))
365 }
366}
367
368/// Backward composition of five throwing functions with shallow cloning optimization.
369///
370/// # Arguments
371/// * `f` - A function that takes a value in `E` and returns a `Result<F, Err>`
372/// * `g` - A function that takes a value in `D` and returns a `Result<E, Err>`
373/// * `h` - A function that takes a value in `C` and returns a `Result<D, Err>`
374/// * `i` - A function that takes a value in `B` and returns a `Result<C, Err>`
375/// * `j` - A function that takes a value in `A` and returns a `Result<B, Err>`
376///
377/// # Returns
378/// A new function that takes a value in `A` and returns a `Result<F, Err>`
379pub fn compose5_throwing<A, B, C, D, E, F, Err, G, H, I, J>(
380 f: F,
381 g: G,
382 h: H,
383 i: I,
384 j: J,
385) -> impl Fn(A) -> Result<F, Err>
386where
387 F: Fn(E) -> Result<F, Err> + 'static,
388 G: Fn(D) -> Result<E, Err> + 'static,
389 H: Fn(C) -> Result<D, Err> + 'static,
390 I: Fn(B) -> Result<C, Err> + 'static,
391 J: Fn(A) -> Result<B, Err> + 'static,
392{
393 let f_rc = Rc::new(f);
394 let g_rc = Rc::new(g);
395 let h_rc = Rc::new(h);
396 let i_rc = Rc::new(i);
397 let j_rc = Rc::new(j);
398 move |a: A| {
399 let f_ref = Rc::clone(&f_rc);
400 let g_ref = Rc::clone(&g_rc);
401 let h_ref = Rc::clone(&h_rc);
402 let i_ref = Rc::clone(&i_rc);
403 let j_ref = Rc::clone(&j_rc);
404 j_ref(a)
405 .and_then(|b| i_ref(b))
406 .and_then(|c| h_ref(c))
407 .and_then(|d| g_ref(d))
408 .and_then(|e| f_ref(e))
409 }
410}
411
412/// Backward composition of six throwing functions with shallow cloning optimization.
413///
414/// # Arguments
415/// * `f` - A function that takes a value in `F` and returns a `Result<G, Err>`
416/// * `g` - A function that takes a value in `E` and returns a `Result<F, Err>`
417/// * `h` - A function that takes a value in `D` and returns a `Result<E, Err>`
418/// * `i` - A function that takes a value in `C` and returns a `Result<D, Err>`
419/// * `j` - A function that takes a value in `B` and returns a `Result<C, Err>`
420/// * `k` - A function that takes a value in `A` and returns a `Result<B, Err>`
421///
422/// # Returns
423/// A new function that takes a value in `A` and returns a `Result<R, Err>`
424pub fn compose6_throwing<A, B, C, D, E, F_IN, R, Err, F, G, H, I, J, K>(
425 f: F,
426 g: G,
427 h: H,
428 i: I,
429 j: J,
430 k: K,
431) -> impl Fn(A) -> Result<R, Err>
432where
433 F: Fn(F_IN) -> Result<R, Err> + 'static,
434 G: Fn(E) -> Result<F_IN, Err> + 'static,
435 H: Fn(D) -> Result<E, Err> + 'static,
436 I: Fn(C) -> Result<D, Err> + 'static,
437 J: Fn(B) -> Result<C, Err> + 'static,
438 K: Fn(A) -> Result<B, Err> + 'static,
439{
440 let f_rc = Rc::new(f);
441 let g_rc = Rc::new(g);
442 let h_rc = Rc::new(h);
443 let i_rc = Rc::new(i);
444 let j_rc = Rc::new(j);
445 let k_rc = Rc::new(k);
446 move |a: A| {
447 let f_ref = Rc::clone(&f_rc);
448 let g_ref = Rc::clone(&g_rc);
449 let h_ref = Rc::clone(&h_rc);
450 let i_ref = Rc::clone(&i_rc);
451 let j_ref = Rc::clone(&j_rc);
452 let k_ref = Rc::clone(&k_rc);
453 k_ref(a)
454 .and_then(|b| j_ref(b))
455 .and_then(|c| i_ref(c))
456 .and_then(|d| h_ref(d))
457 .and_then(|e| g_ref(e))
458 .and_then(|f_val| f_ref(f_val))
459 }
460}
461
462#[cfg(test)]
463mod tests {
464 use super::*;
465
466 #[test]
467 fn test_compose_basic() {
468 let add_one = |x: i32| x + 1;
469 let multiply_by_two = |x: i32| x * 2;
470 let composed = compose(multiply_by_two, add_one);
471
472 assert_eq!(composed(5), 12); // (5 + 1) * 2 = 12
473 }
474
475 #[test]
476 fn test_compose3() {
477 let add_one = |x: i32| x + 1;
478 let multiply_by_two = |x: i32| x * 2;
479 let subtract_three = |x: i32| x - 3;
480 let composed = compose3(subtract_three, multiply_by_two, add_one);
481
482 assert_eq!(composed(5), 9); // ((5 + 1) * 2) - 3 = 9
483 }
484
485 #[test]
486 fn test_compose_throwing() {
487 let parse_int = |s: &str| s.parse::<i32>().map_err(|_| "Parse error");
488 let add_one = |x: i32| Ok(x + 1);
489 let composed = compose_throwing(add_one, parse_int);
490
491 assert_eq!(composed("5"), Ok(6));
492 assert_eq!(composed("invalid"), Err("Parse error"));
493 }
494
495 #[test]
496 fn test_compose3_throwing() {
497 let parse_int = |s: &str| s.parse::<i32>().map_err(|_| "Parse error");
498 let add_one = |x: i32| Ok(x + 1);
499 let multiply_by_two = |x: i32| Ok(x * 2);
500 let composed = compose3_throwing(multiply_by_two, add_one, parse_int);
501
502 assert_eq!(composed("5"), Ok(12)); // ((5 + 1) * 2) = 12
503 assert_eq!(composed("invalid"), Err("Parse error"));
504 }
505}