fp_library/classes/par_foldable.rs
1//! Data structures that can be folded in parallel using thread-safe functions.
2//!
3//! **Note: The `rayon` feature must be enabled to use parallel iteration.**
4//!
5//! ### Examples
6//!
7//! ```
8//! use fp_library::{brands::*, functions::*};
9//!
10//! let v = vec![1, 2, 3, 4, 5];
11//! let f = send_cloneable_fn_new::<ArcFnBrand, _, _>(|x: i32| x.to_string());
12//! let result: String = par_fold_map::<ArcFnBrand, VecBrand, _, _>(f, v);
13//! assert_eq!(result, "12345");
14//! ```
15
16use fp_macros::doc_params;
17use fp_macros::doc_type_params;
18use fp_macros::hm_signature;
19
20use super::{foldable::Foldable, monoid::Monoid, send_cloneable_fn::SendCloneableFn};
21use crate::{Apply, kinds::*, types::SendEndofunction};
22
23/// A type class for data structures that can be folded in parallel.
24///
25/// This trait provides parallel versions of `Foldable` operations that require
26/// `Send + Sync` bounds on elements and functions. It uses the branded
27/// `SendOf` function type to maintain the library's HKT abstraction.
28///
29/// **Note: The `rayon` feature must be enabled to use parallel iteration.**
30///
31/// ### Minimal Implementation
32///
33/// A minimal implementation requires [`ParFoldable::par_fold_map`].
34///
35/// ### Thread Safety
36///
37/// All operations in this trait are designed to be safe for parallel execution:
38/// - Element type `A` must be `Send + Sync`
39/// - Accumulator/result types must be `Send + Sync`
40/// - Functions are wrapped in `FnBrand::SendOf` which guarantees `Send + Sync`
41///
42/// ### Why is `FnBrand` a Trait-Level Parameter?
43///
44/// Unlike [`Foldable`] where `FnBrand` is a method-level generic parameter
45/// and functions are raw `Fn` types wrapped internally, `ParFoldable` takes `FnBrand`
46/// at the trait level. This design choice is motivated by:
47///
48/// 1. **Thread-safe function values as first-class HKT types**: Function parameters
49/// like `func` in [`ParFoldable::par_fold_map`] are HKT-applied types via
50/// `Apply!(brand: FnBrand, kind: SendCloneableFn, output: SendOf, ...)`. This allows
51/// the type system to enforce thread-safety at the API boundary.
52///
53/// 2. **Guaranteed `Send + Sync` bounds**: The `output: SendOf` in the `Apply!` macro
54/// ensures the function type carries `Send + Sync` bounds essential for parallel
55/// execution, rather than relying on runtime checks.
56///
57/// 3. **Default implementation requirements**: The default [`ParFoldable::par_fold_right`]
58/// implementation needs to call `<FnBrand as SendCloneableFn>::send_cloneable_fn_new(...)` to
59/// create new wrapped functions. Having `FnBrand` at the trait level makes it
60/// available throughout the implementation.
61///
62/// 4. **Multiple implementations per data structure**: With trait-level parameterization,
63/// a type can implement `ParFoldable<ArcFnBrand>` and potentially other function
64/// brands, allowing callers to choose the appropriate thread-safe function wrapper.
65///
66/// ### Examples
67///
68/// ```
69/// use fp_library::{brands::*, functions::*};
70///
71/// let v = vec![1, 2, 3, 4, 5];
72/// let f = send_cloneable_fn_new::<ArcFnBrand, _, _>(|x: i32| x.to_string());
73/// let result: String = par_fold_map::<ArcFnBrand, VecBrand, _, _>(f, v);
74/// assert_eq!(result, "12345");
75/// ```
76pub trait ParFoldable: Foldable {
77 /// Parallel version of fold_map.
78 ///
79 /// Maps each element to a monoid value using `func`, then combines all values using the monoid's `append` operation. The mapping operations may be executed in parallel.
80 ///
81 /// ### Type Signature
82 ///
83 #[hm_signature(ParFoldable)]
84 ///
85 /// ### Type Parameters
86 ///
87 #[doc_type_params(
88 "The lifetime of the computation.",
89 "The brand of thread-safe function to use.",
90 "The element type.",
91 "The monoid type."
92 )]
93 ///
94 /// ### Parameters
95 ///
96 #[doc_params(
97 "The thread-safe function to map each element to a monoid`.",
98 "The foldable structure."
99 )]
100 ///
101 /// ### Returns
102 ///
103 /// The combined monoid value
104 ///
105 /// ### Examples
106 ///
107 /// ```
108 /// use fp_library::{brands::*, functions::*};
109 ///
110 /// let v = vec![1, 2, 3, 4, 5];
111 /// let f = send_cloneable_fn_new::<ArcFnBrand, _, _>(|x: i32| x.to_string());
112 /// let result: String = par_fold_map::<ArcFnBrand, VecBrand, _, _>(f, v);
113 /// assert_eq!(result, "12345");
114 /// ```
115 fn par_fold_map<'a, FnBrand, A, M>(
116 func: <FnBrand as SendCloneableFn>::SendOf<'a, A, M>,
117 fa: Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>),
118 ) -> M
119 where
120 FnBrand: 'a + SendCloneableFn,
121 A: 'a + Clone + Send + Sync,
122 M: Monoid + Send + Sync + 'a;
123
124 /// Parallel version of fold_right.
125 ///
126 /// Folds the structure by applying a function from right to left, potentially in parallel.
127 ///
128 /// ### Type Signature
129 ///
130 #[hm_signature(ParFoldable)]
131 ///
132 /// ### Type Parameters
133 ///
134 #[doc_type_params(
135 "The lifetime of the computation.",
136 "The brand of thread-safe function to use.",
137 "The element type.",
138 "The accumulator type."
139 )]
140 ///
141 /// ### Parameters
142 ///
143 #[doc_params(
144 "The thread-safe function to apply to each element and the accumulator.",
145 "The initial value of the accumulator.",
146 "The structure to fold."
147 )]
148 ///
149 /// ### Returns
150 ///
151 /// The final accumulator value
152 ///
153 /// ### Examples
154 ///
155 /// ```
156 /// use fp_library::{brands::*, functions::*};
157 ///
158 /// let v = vec![1, 2, 3, 4, 5];
159 /// let f = send_cloneable_fn_new::<ArcFnBrand, _, _>(|(a, b)| a + b);
160 /// let sum = par_fold_right::<ArcFnBrand, VecBrand, _, _>(f, 10, v);
161 /// assert_eq!(sum, 25);
162 /// ```
163 fn par_fold_right<'a, FnBrand, A, B>(
164 func: <FnBrand as SendCloneableFn>::SendOf<'a, (A, B), B>,
165 init: B,
166 fa: Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>),
167 ) -> B
168 where
169 A: 'a + Clone + Send + Sync,
170 B: Send + Sync + 'a,
171 FnBrand: 'a + SendCloneableFn,
172 {
173 let f_clone = func.clone();
174 let endo = Self::par_fold_map::<FnBrand, _, _>(
175 <FnBrand as SendCloneableFn>::send_cloneable_fn_new(move |a: A| {
176 let f_inner = f_clone.clone();
177 SendEndofunction::<FnBrand, B>::new(
178 <FnBrand as SendCloneableFn>::send_cloneable_fn_new(move |b: B| {
179 f_inner((a.clone(), b))
180 }),
181 )
182 }),
183 fa,
184 );
185 endo.0(init)
186 }
187}
188
189/// Parallel fold_map operation.
190///
191/// Free function version that dispatches to [the type class' associated function][`ParFoldable::par_fold_map`].
192///
193/// ### Type Signature
194///
195#[hm_signature(ParFoldable)]
196///
197/// ### Type Parameters
198///
199#[doc_type_params(
200 "The lifetime of the computation.",
201 "The brand of thread-safe function to use.",
202 "The brand of the foldable structure.",
203 "The element type.",
204 "The monoid type."
205)]
206///
207/// ### Parameters
208///
209#[doc_params("The thread-safe function to map each element to a monoid.", "The structure to fold.")]
210///
211/// ### Returns
212///
213/// The combined monoid value.
214///
215/// ### Examples
216///
217/// ```
218/// use fp_library::{brands::*, functions::*};
219///
220/// let v = vec![1, 2, 3, 4, 5];
221/// let f = send_cloneable_fn_new::<ArcFnBrand, _, _>(|x: i32| x.to_string());
222/// let result: String = par_fold_map::<ArcFnBrand, VecBrand, _, _>(f, v);
223/// assert_eq!(result, "12345");
224/// ```
225pub fn par_fold_map<'a, FnBrand, F, A, M>(
226 func: <FnBrand as SendCloneableFn>::SendOf<'a, A, M>,
227 fa: Apply!(<F as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>),
228) -> M
229where
230 FnBrand: 'a + SendCloneableFn,
231 F: ParFoldable,
232 A: 'a + Clone + Send + Sync,
233 M: Monoid + Send + Sync + 'a,
234{
235 F::par_fold_map::<FnBrand, A, M>(func, fa)
236}
237
238/// Parallel fold_right operation.
239///
240/// Free function version that dispatches to [the type class' associated function][`ParFoldable::par_fold_right`].
241///
242/// ### Type Signature
243///
244#[hm_signature(ParFoldable)]
245///
246/// ### Type Parameters
247///
248#[doc_type_params(
249 "The lifetime of the computation.",
250 "The brand of thread-safe function to use.",
251 "The brand of the foldable structure.",
252 "The element type.",
253 "The accumulator type."
254)]
255///
256/// ### Parameters
257///
258#[doc_params(
259 "The thread-safe function to apply to each element and the accumulator.",
260 "The initial value of the accumulator.",
261 "The structure to fold."
262)]
263///
264/// ### Returns
265///
266/// The final accumulator value.
267///
268/// ### Examples
269///
270/// ```
271/// use fp_library::{brands::*, functions::*};
272///
273/// let v = vec![1, 2, 3, 4, 5];
274/// let f = send_cloneable_fn_new::<ArcFnBrand, _, _>(|(a, b)| a + b);
275/// let sum = par_fold_right::<ArcFnBrand, VecBrand, _, _>(f, 10, v);
276/// assert_eq!(sum, 25);
277/// ```
278pub fn par_fold_right<'a, FnBrand, F, A, B>(
279 func: <FnBrand as SendCloneableFn>::SendOf<'a, (A, B), B>,
280 initial: B,
281 fa: Apply!(<F as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>),
282) -> B
283where
284 FnBrand: SendCloneableFn + 'a,
285 F: ParFoldable,
286 A: 'a + Clone + Send + Sync,
287 B: Send + Sync + 'a,
288{
289 F::par_fold_right::<FnBrand, A, B>(func, initial, fa)
290}