qubit_function/functions/bi_function_once/fn_bi_function_once_ops.rs
1/*******************************************************************************
2 *
3 * Copyright (c) 2025 - 2026 Haixing Hu.
4 *
5 * SPDX-License-Identifier: Apache-2.0
6 *
7 * Licensed under the Apache License, Version 2.0.
8 *
9 ******************************************************************************/
10// qubit-style: allow explicit-imports
11//! Defines the `FnBiFunctionOnceOps` public type.
12
13use super::{
14 BiPredicate,
15 BoxBiFunctionOnce,
16 BoxConditionalBiFunctionOnce,
17};
18
19// ============================================================================
20// FnBiFunctionOnceOps - Extension trait for FnOnce(&T, &U) -> R bi-functions
21// ============================================================================
22
23/// Extension trait for closures implementing `FnOnce(&T, &U) -> R`
24///
25/// Provides composition methods (`and_then`, `when`) for one-time use
26/// bi-function closures and function pointers without requiring explicit
27/// wrapping in `BoxBiFunctionOnce`.
28///
29/// This trait is automatically implemented for all closures and function
30/// pointers that implement `FnOnce(&T, &U) -> R`.
31///
32/// # Design Rationale
33///
34/// While closures automatically implement `BiFunctionOnce<T, U, R>` through
35/// blanket implementation, they don't have access to instance methods like
36/// `and_then` and `when`. This extension trait provides those methods,
37/// returning `BoxBiFunctionOnce` for maximum flexibility.
38///
39/// # Examples
40///
41/// ## Chain composition with and_then
42///
43/// ```rust
44/// use qubit_function::{BiFunctionOnce, FnBiFunctionOnceOps, FunctionOnce};
45///
46/// let add = |x: &i32, y: &i32| *x + *y;
47/// let double = |x: &i32| x * 2;
48///
49/// let composed = add.and_then(double);
50/// assert_eq!(composed.apply(&3, &5), 16); // (3 + 5) * 2
51/// ```
52///
53/// ## Conditional execution with when
54///
55/// ```rust
56/// use qubit_function::{BiFunctionOnce, FnBiFunctionOnceOps};
57///
58/// let add = |x: &i32, y: &i32| *x + *y;
59/// let multiply = |x: &i32, y: &i32| *x * *y;
60///
61/// let conditional = add.when(|x: &i32, y: &i32| *x > 0 && *y > 0).or_else(multiply);
62/// assert_eq!(conditional.apply(&5, &3), 8); // when branch executed
63/// ```
64///
65pub trait FnBiFunctionOnceOps<T, U, R>: FnOnce(&T, &U) -> R + Sized {
66 /// Chain composition - applies self first, then after
67 ///
68 /// Creates a new bi-function that applies this bi-function first,
69 /// then applies the after function to the result. Consumes self and
70 /// returns a `BoxBiFunctionOnce`.
71 ///
72 /// # Type Parameters
73 ///
74 /// * `S` - The output type of the after function
75 /// * `F` - The type of the after function (must implement FunctionOnce<R, S>)
76 ///
77 /// # Parameters
78 ///
79 /// * `after` - The function to apply after self. **Note: This parameter
80 /// is passed by value and will transfer ownership.** Since this is a
81 /// `FnOnce` bi-function, the parameter will be consumed. Can be:
82 /// - A closure: `|x: R| -> S`
83 /// - A function pointer: `fn(R) -> S`
84 /// - A `BoxFunctionOnce<R, S>`
85 /// - Any type implementing `FunctionOnce<R, S>`
86 ///
87 /// # Returns
88 ///
89 /// A new `BoxBiFunctionOnce<T, U, S>` representing the composition
90 ///
91 /// # Examples
92 ///
93 /// ```rust
94 /// use qubit_function::{BiFunctionOnce, FnBiFunctionOnceOps,
95 /// BoxFunctionOnce};
96 ///
97 /// let add = |x: &i32, y: &i32| *x + *y;
98 /// let to_string = BoxFunctionOnce::new(|x: &i32| x.to_string());
99 ///
100 /// // to_string is moved and consumed
101 /// let composed = add.and_then(to_string);
102 /// assert_eq!(composed.apply(&20, &22), "42");
103 /// // to_string.apply(10); // Would not compile - moved
104 /// ```
105 fn and_then<S, F>(self, after: F) -> BoxBiFunctionOnce<T, U, S>
106 where
107 Self: 'static,
108 S: 'static,
109 F: crate::functions::function_once::FunctionOnce<R, S> + 'static,
110 T: 'static,
111 U: 'static,
112 R: 'static,
113 {
114 BoxBiFunctionOnce::new(move |t: &T, u: &U| after.apply(&self(t, u)))
115 }
116
117 /// Creates a conditional bi-function
118 ///
119 /// Returns a bi-function that only executes when a bi-predicate is
120 /// satisfied. You must call `or_else()` to provide an alternative
121 /// bi-function for when the condition is not satisfied.
122 ///
123 /// # Parameters
124 ///
125 /// * `predicate` - The condition to check. **Note: This parameter is passed
126 /// by value and will transfer ownership.** If you need to preserve the
127 /// original bi-predicate, clone it first (if it implements `Clone`).
128 /// Can be:
129 /// - A closure: `|x: &T, y: &U| -> bool`
130 /// - A function pointer: `fn(&T, &U) -> bool`
131 /// - A `BoxBiPredicate<T, U>`
132 /// - An `RcBiPredicate<T, U>`
133 /// - An `ArcBiPredicate<T, U>`
134 /// - Any type implementing `BiPredicate<T, U>`
135 ///
136 /// # Returns
137 ///
138 /// Returns `BoxConditionalBiFunctionOnce<T, U, R>`
139 ///
140 /// # Examples
141 ///
142 /// ## Basic usage with or_else
143 ///
144 /// ```rust
145 /// use qubit_function::{BiFunctionOnce, FnBiFunctionOnceOps};
146 ///
147 /// let add = |x: &i32, y: &i32| *x + *y;
148 /// let multiply = |x: &i32, y: &i32| *x * *y;
149 /// let conditional = add.when(|x: &i32, y: &i32| *x > 0)
150 /// .or_else(multiply);
151 ///
152 /// assert_eq!(conditional.apply(&5, &3), 8);
153 /// ```
154 ///
155 /// ## Preserving bi-predicate with clone
156 ///
157 /// ```rust
158 /// use qubit_function::{BiFunctionOnce, FnBiFunctionOnceOps,
159 /// RcBiPredicate};
160 ///
161 /// let add = |x: &i32, y: &i32| *x + *y;
162 /// let both_positive = RcBiPredicate::new(|x: &i32, y: &i32|
163 /// *x > 0 && *y > 0);
164 ///
165 /// // Clone to preserve original bi-predicate
166 /// let conditional = add.when(both_positive.clone())
167 /// .or_else(|x: &i32, y: &i32| *x * *y);
168 ///
169 /// assert_eq!(conditional.apply(&5, &3), 8);
170 ///
171 /// // Original bi-predicate still usable
172 /// use qubit_function::BiPredicate;
173 /// assert!(both_positive.test(&5, &3));
174 /// ```
175 fn when<P>(self, predicate: P) -> BoxConditionalBiFunctionOnce<T, U, R>
176 where
177 Self: 'static,
178 P: BiPredicate<T, U> + 'static,
179 T: 'static,
180 U: 'static,
181 R: 'static,
182 {
183 BoxBiFunctionOnce::new(self).when(predicate)
184 }
185}
186
187/// Blanket implementation of FnBiFunctionOnceOps for all closures
188///
189/// Automatically implements `FnBiFunctionOnceOps<T, U, R>` for any type that
190/// implements `FnOnce(&T, &U) -> R`.
191///
192impl<T, U, R, F> FnBiFunctionOnceOps<T, U, R> for F
193where
194 F: FnOnce(&T, &U) -> R,
195{
196 // empty
197}