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::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}