Skip to main content

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::document_parameters;
17use fp_macros::document_signature;
18use fp_macros::document_type_parameters;
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	#[document_signature]
84	///
85	/// ### Type Parameters
86	///
87	#[document_type_parameters(
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	#[document_parameters(
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	#[document_signature]
131	///
132	/// ### Type Parameters
133	///
134	#[document_type_parameters(
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	#[document_parameters(
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#[document_signature]
196///
197/// ### Type Parameters
198///
199#[document_type_parameters(
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#[document_parameters(
210	"The thread-safe function to map each element to a monoid.",
211	"The structure to fold."
212)]
213///
214/// ### Returns
215///
216/// The combined monoid value.
217///
218/// ### Examples
219///
220/// ```
221/// use fp_library::{brands::*, functions::*};
222///
223/// let v = vec![1, 2, 3, 4, 5];
224/// let f = send_cloneable_fn_new::<ArcFnBrand, _, _>(|x: i32| x.to_string());
225/// let result: String = par_fold_map::<ArcFnBrand, VecBrand, _, _>(f, v);
226/// assert_eq!(result, "12345");
227/// ```
228pub fn par_fold_map<'a, FnBrand, F, A, M>(
229	func: <FnBrand as SendCloneableFn>::SendOf<'a, A, M>,
230	fa: Apply!(<F as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>),
231) -> M
232where
233	FnBrand: 'a + SendCloneableFn,
234	F: ParFoldable,
235	A: 'a + Clone + Send + Sync,
236	M: Monoid + Send + Sync + 'a,
237{
238	F::par_fold_map::<FnBrand, A, M>(func, fa)
239}
240
241/// Parallel fold_right operation.
242///
243/// Free function version that dispatches to [the type class' associated function][`ParFoldable::par_fold_right`].
244///
245/// ### Type Signature
246///
247#[document_signature]
248///
249/// ### Type Parameters
250///
251#[document_type_parameters(
252	"The lifetime of the computation.",
253	"The brand of thread-safe function to use.",
254	"The brand of the foldable structure.",
255	"The element type.",
256	"The accumulator type."
257)]
258///
259/// ### Parameters
260///
261#[document_parameters(
262	"The thread-safe function to apply to each element and the accumulator.",
263	"The initial value of the accumulator.",
264	"The structure to fold."
265)]
266///
267/// ### Returns
268///
269/// The final accumulator value.
270///
271/// ### Examples
272///
273/// ```
274/// use fp_library::{brands::*, functions::*};
275///
276/// let v = vec![1, 2, 3, 4, 5];
277/// let f = send_cloneable_fn_new::<ArcFnBrand, _, _>(|(a, b)| a + b);
278/// let sum = par_fold_right::<ArcFnBrand, VecBrand, _, _>(f, 10, v);
279/// assert_eq!(sum, 25);
280/// ```
281pub fn par_fold_right<'a, FnBrand, F, A, B>(
282	func: <FnBrand as SendCloneableFn>::SendOf<'a, (A, B), B>,
283	initial: B,
284	fa: Apply!(<F as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>),
285) -> B
286where
287	FnBrand: SendCloneableFn + 'a,
288	F: ParFoldable,
289	A: 'a + Clone + Send + Sync,
290	B: Send + Sync + 'a,
291{
292	F::par_fold_right::<FnBrand, A, B>(func, initial, fa)
293}