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}