Skip to main content

fp_library/classes/
ref_bifoldable.rs

1//! By-reference variant of [`Bifoldable`](crate::classes::Bifoldable).
2//!
3//! **User story:** "I want to fold over a bifoldable value without consuming it."
4//!
5//! This trait is for types where the container holds cached or borrowed values
6//! accessible by reference. The closures receive `&A` and `&B` instead of `A`
7//! and `B`, avoiding unnecessary moves. `A: Clone` and `B: Clone` are required
8//! so that default implementations can own values extracted from references.
9//!
10//! ### Examples
11//!
12//! ```
13//! use fp_library::{
14//! 	brands::*,
15//! 	functions::explicit::*,
16//! };
17//!
18//! let x: Result<i32, i32> = Ok(5);
19//! let y = bi_fold_map::<RcFnBrand, ResultBrand, _, _, _, _, _>(
20//! 	(|e: &i32| e.to_string(), |s: &i32| s.to_string()),
21//! 	&x,
22//! );
23//! assert_eq!(y, "5".to_string());
24//! ```
25
26#[fp_macros::document_module]
27mod inner {
28	use {
29		crate::{
30			classes::*,
31			kinds::*,
32			types::Endofunction,
33		},
34		fp_macros::*,
35	};
36
37	/// By-reference folding over a bifoldable structure.
38	///
39	/// Similar to [`Bifoldable`], but closures receive `&A` and `&B` instead of
40	/// owned values. This is the honest interface for types that internally hold
41	/// cached or borrowed values and would otherwise force a clone to satisfy the
42	/// by-value `Bifoldable` signature.
43	///
44	/// ### Minimal Implementation
45	///
46	/// A minimal implementation requires either [`RefBifoldable::ref_bi_fold_map`] or
47	/// [`RefBifoldable::ref_bi_fold_right`] to be defined directly:
48	///
49	/// * If [`RefBifoldable::ref_bi_fold_right`] is implemented,
50	///   [`RefBifoldable::ref_bi_fold_map`] and [`RefBifoldable::ref_bi_fold_left`]
51	///   are derived from it.
52	/// * If [`RefBifoldable::ref_bi_fold_map`] is implemented,
53	///   [`RefBifoldable::ref_bi_fold_right`] is derived via `Endofunction`, and
54	///   [`RefBifoldable::ref_bi_fold_left`] is derived from the derived
55	///   [`RefBifoldable::ref_bi_fold_right`].
56	///
57	/// Note: defining both defaults creates a circular dependency and will not terminate.
58	///
59	/// ### Laws
60	///
61	/// `RefBifoldable` instances must be internally consistent and consistent with
62	/// `Bifunctor` when one is also defined:
63	/// * ref_bi_fold_map/ref_bi_fold_right consistency: `ref_bi_fold_map(f, g, &x) = ref_bi_fold_right(|a, c| append(f(a), c), |b, c| append(g(b), c), empty(), &x)`.
64	#[document_examples]
65	///
66	/// RefBifoldable laws for [`Result`]:
67	///
68	/// ```
69	/// use fp_library::{
70	/// 	brands::*,
71	/// 	functions::{
72	/// 		explicit::{
73	/// 			bi_fold_map,
74	/// 			bi_fold_right,
75	/// 		},
76	/// 		*,
77	/// 	},
78	/// };
79	///
80	/// // ResultBrand has Of<E, A> = Result<A, E>, so the first function handles errors
81	/// // and the second function handles ok values.
82	/// let ok: Result<i32, String> = Ok(5);
83	/// let err: Result<i32, String> = Err("err".to_string());
84	/// let f = |e: &String| format!("err:{e}");
85	/// let g = |a: &i32| a.to_string();
86	///
87	/// // bi_fold_map/bi_fold_right consistency with ref closures (Ok case):
88	/// assert_eq!(
89	/// 	bi_fold_map::<RcFnBrand, ResultBrand, _, _, _, _, _>((f, g), &ok),
90	/// 	bi_fold_right::<RcFnBrand, ResultBrand, _, _, _, _, _>(
91	/// 		(|a: &String, c: String| append(f(a), c), |b: &i32, c: String| append(g(b), c)),
92	/// 		empty::<String>(),
93	/// 		&ok,
94	/// 	),
95	/// );
96	///
97	/// // bi_fold_map/bi_fold_right consistency with ref closures (Err case):
98	/// assert_eq!(
99	/// 	bi_fold_map::<RcFnBrand, ResultBrand, _, _, _, _, _>((f, g), &err),
100	/// 	bi_fold_right::<RcFnBrand, ResultBrand, _, _, _, _, _>(
101	/// 		(|a: &String, c: String| append(f(a), c), |b: &i32, c: String| append(g(b), c)),
102	/// 		empty::<String>(),
103	/// 		&err,
104	/// 	),
105	/// );
106	/// ```
107	#[kind(type Of<'a, A: 'a, B: 'a>: 'a;)]
108	pub trait RefBifoldable {
109		/// Folds the bifoldable structure from right to left by reference using two step functions.
110		///
111		/// This method performs a right-associative fold, dispatching to `f` for
112		/// elements of the first type and `g` for elements of the second type.
113		/// Closures receive references rather than owned values.
114		#[document_signature]
115		///
116		#[document_type_parameters(
117			"The lifetime of the values.",
118			"The brand of the cloneable function to use.",
119			"The type of the first-position elements.",
120			"The type of the second-position elements.",
121			"The type of the accumulator."
122		)]
123		///
124		#[document_parameters(
125			"The step function for first-position element references.",
126			"The step function for second-position element references.",
127			"The initial accumulator value.",
128			"The bifoldable structure to fold (borrowed)."
129		)]
130		///
131		#[document_returns("The final accumulator value after folding all elements.")]
132		#[document_examples]
133		///
134		/// ```
135		/// use fp_library::{
136		/// 	brands::*,
137		/// 	functions::explicit::*,
138		/// };
139		///
140		/// let x: Result<i32, i32> = Err(3);
141		/// let y = bi_fold_right::<RcFnBrand, ResultBrand, _, _, _, _, _>(
142		/// 	(|e: &i32, acc| acc - *e, |s: &i32, acc| acc + *s),
143		/// 	10,
144		/// 	&x,
145		/// );
146		/// assert_eq!(y, 7);
147		/// ```
148		fn ref_bi_fold_right<'a, FnBrand: LiftFn + 'a, A: 'a + Clone, B: 'a + Clone, C: 'a>(
149			f: impl Fn(&A, C) -> C + 'a,
150			g: impl Fn(&B, C) -> C + 'a,
151			z: C,
152			p: &Apply!(<Self as Kind!( type Of<'a, A: 'a, B: 'a>: 'a; )>::Of<'a, A, B>),
153		) -> C {
154			let f = <FnBrand as LiftFn>::new(move |(a, c): (A, C)| f(&a, c));
155			let g = <FnBrand as LiftFn>::new(move |(b, c): (B, C)| g(&b, c));
156			let endo = Self::ref_bi_fold_map::<FnBrand, A, B, Endofunction<'a, FnBrand, C>>(
157				move |a: &A| {
158					let a = a.clone();
159					let f = f.clone();
160					Endofunction::<FnBrand, C>::new(<FnBrand as LiftFn>::new(move |c| {
161						f((a.clone(), c))
162					}))
163				},
164				move |b: &B| {
165					let b = b.clone();
166					let g = g.clone();
167					Endofunction::<FnBrand, C>::new(<FnBrand as LiftFn>::new(move |c| {
168						g((b.clone(), c))
169					}))
170				},
171				p,
172			);
173			endo.0(z)
174		}
175
176		/// Folds the bifoldable structure from left to right by reference using two step functions.
177		///
178		/// This method performs a left-associative fold, dispatching to `f` for
179		/// elements of the first type and `g` for elements of the second type.
180		/// Closures receive references rather than owned values.
181		#[document_signature]
182		///
183		#[document_type_parameters(
184			"The lifetime of the values.",
185			"The brand of the cloneable function to use.",
186			"The type of the first-position elements.",
187			"The type of the second-position elements.",
188			"The type of the accumulator."
189		)]
190		///
191		#[document_parameters(
192			"The step function for first-position element references.",
193			"The step function for second-position element references.",
194			"The initial accumulator value.",
195			"The bifoldable structure to fold (borrowed)."
196		)]
197		///
198		#[document_returns("The final accumulator value after folding all elements.")]
199		#[document_examples]
200		///
201		/// ```
202		/// use fp_library::{
203		/// 	brands::*,
204		/// 	functions::explicit::*,
205		/// };
206		///
207		/// let x: Result<i32, i32> = Ok(5);
208		/// let y = bi_fold_left::<RcFnBrand, ResultBrand, _, _, _, _, _>(
209		/// 	(|acc, e: &i32| acc - *e, |acc, s: &i32| acc + *s),
210		/// 	10,
211		/// 	&x,
212		/// );
213		/// assert_eq!(y, 15);
214		/// ```
215		fn ref_bi_fold_left<'a, FnBrand: LiftFn + 'a, A: 'a + Clone, B: 'a + Clone, C: 'a>(
216			f: impl Fn(C, &A) -> C + 'a,
217			g: impl Fn(C, &B) -> C + 'a,
218			z: C,
219			p: &Apply!(<Self as Kind!( type Of<'a, A: 'a, B: 'a>: 'a; )>::Of<'a, A, B>),
220		) -> C {
221			let f = <FnBrand as LiftFn>::new(move |(c, a): (C, A)| f(c, &a));
222			let g = <FnBrand as LiftFn>::new(move |(c, b): (C, B)| g(c, &b));
223			let endo = Self::ref_bi_fold_right::<FnBrand, A, B, Endofunction<'a, FnBrand, C>>(
224				move |a: &A, k: Endofunction<'a, FnBrand, C>| {
225					let a = a.clone();
226					let f = f.clone();
227					let current =
228						Endofunction::<FnBrand, C>::new(<FnBrand as LiftFn>::new(move |c| {
229							f((c, a.clone()))
230						}));
231					Semigroup::append(k, current)
232				},
233				move |b: &B, k: Endofunction<'a, FnBrand, C>| {
234					let b = b.clone();
235					let g = g.clone();
236					let current =
237						Endofunction::<FnBrand, C>::new(<FnBrand as LiftFn>::new(move |c| {
238							g((c, b.clone()))
239						}));
240					Semigroup::append(k, current)
241				},
242				Endofunction::<FnBrand, C>::empty(),
243				p,
244			);
245			endo.0(z)
246		}
247
248		/// Maps elements of both types to a monoid by reference and combines the results.
249		///
250		/// This method maps each element of the first type using `f` and each element
251		/// of the second type using `g`, receiving references rather than owned values,
252		/// then combines all results using the monoid's `append` operation.
253		#[document_signature]
254		///
255		#[document_type_parameters(
256			"The lifetime of the values.",
257			"The brand of the cloneable function to use.",
258			"The type of the first-position elements.",
259			"The type of the second-position elements.",
260			"The monoid type to fold into."
261		)]
262		///
263		#[document_parameters(
264			"The function mapping first-position element references to the monoid.",
265			"The function mapping second-position element references to the monoid.",
266			"The bifoldable structure to fold (borrowed)."
267		)]
268		///
269		#[document_returns("The combined monoid value.")]
270		#[document_examples]
271		///
272		/// ```
273		/// use fp_library::{
274		/// 	brands::*,
275		/// 	functions::explicit::*,
276		/// };
277		///
278		/// let x: Result<i32, i32> = Ok(5);
279		/// let y = bi_fold_map::<RcFnBrand, ResultBrand, _, _, _, _, _>(
280		/// 	(|e: &i32| e.to_string(), |s: &i32| s.to_string()),
281		/// 	&x,
282		/// );
283		/// assert_eq!(y, "5".to_string());
284		/// ```
285		fn ref_bi_fold_map<'a, FnBrand: LiftFn + 'a, A: 'a + Clone, B: 'a + Clone, M>(
286			f: impl Fn(&A) -> M + 'a,
287			g: impl Fn(&B) -> M + 'a,
288			p: &Apply!(<Self as Kind!( type Of<'a, A: 'a, B: 'a>: 'a; )>::Of<'a, A, B>),
289		) -> M
290		where
291			M: Monoid + 'a, {
292			Self::ref_bi_fold_right::<FnBrand, A, B, M>(
293				move |a, m| M::append(f(a), m),
294				move |b, m| M::append(g(b), m),
295				M::empty(),
296				p,
297			)
298		}
299	}
300
301	/// Folds the bifoldable structure from right to left by reference using two step functions.
302	///
303	/// Free function version that dispatches to [the type class' associated function][`RefBifoldable::ref_bi_fold_right`].
304	#[document_signature]
305	///
306	#[document_type_parameters(
307		"The lifetime of the values.",
308		"The brand of the cloneable function to use.",
309		"The brand of the bifoldable structure.",
310		"The type of the first-position elements.",
311		"The type of the second-position elements.",
312		"The type of the accumulator."
313	)]
314	///
315	#[document_parameters(
316		"The step function for first-position element references.",
317		"The step function for second-position element references.",
318		"The initial accumulator value.",
319		"The bifoldable structure to fold (borrowed)."
320	)]
321	///
322	#[document_returns("The final accumulator value after folding all elements.")]
323	#[document_examples]
324	///
325	/// ```
326	/// use fp_library::{
327	/// 	brands::*,
328	/// 	functions::explicit::*,
329	/// };
330	///
331	/// let x: Result<i32, i32> = Err(3);
332	/// let y = bi_fold_right::<RcFnBrand, ResultBrand, _, _, _, _, _>(
333	/// 	(|e: &i32, acc| acc - *e, |s: &i32, acc| acc + *s),
334	/// 	10,
335	/// 	&x,
336	/// );
337	/// assert_eq!(y, 7);
338	/// ```
339	pub fn ref_bi_fold_right<
340		'a,
341		FnBrand: LiftFn + 'a,
342		Brand: RefBifoldable,
343		A: 'a + Clone,
344		B: 'a + Clone,
345		C: 'a,
346	>(
347		f: impl Fn(&A, C) -> C + 'a,
348		g: impl Fn(&B, C) -> C + 'a,
349		z: C,
350		p: &Apply!(<Brand as Kind!( type Of<'a, A: 'a, B: 'a>: 'a; )>::Of<'a, A, B>),
351	) -> C {
352		Brand::ref_bi_fold_right::<FnBrand, A, B, C>(f, g, z, p)
353	}
354
355	/// Folds the bifoldable structure from left to right by reference using two step functions.
356	///
357	/// Free function version that dispatches to [the type class' associated function][`RefBifoldable::ref_bi_fold_left`].
358	#[document_signature]
359	///
360	#[document_type_parameters(
361		"The lifetime of the values.",
362		"The brand of the cloneable function to use.",
363		"The brand of the bifoldable structure.",
364		"The type of the first-position elements.",
365		"The type of the second-position elements.",
366		"The type of the accumulator."
367	)]
368	///
369	#[document_parameters(
370		"The step function for first-position element references.",
371		"The step function for second-position element references.",
372		"The initial accumulator value.",
373		"The bifoldable structure to fold (borrowed)."
374	)]
375	///
376	#[document_returns("The final accumulator value after folding all elements.")]
377	#[document_examples]
378	///
379	/// ```
380	/// use fp_library::{
381	/// 	brands::*,
382	/// 	functions::explicit::*,
383	/// };
384	///
385	/// let x: Result<i32, i32> = Ok(5);
386	/// let y = bi_fold_left::<RcFnBrand, ResultBrand, _, _, _, _, _>(
387	/// 	(|acc, e: &i32| acc - *e, |acc, s: &i32| acc + *s),
388	/// 	10,
389	/// 	&x,
390	/// );
391	/// assert_eq!(y, 15);
392	/// ```
393	pub fn ref_bi_fold_left<
394		'a,
395		FnBrand: LiftFn + 'a,
396		Brand: RefBifoldable,
397		A: 'a + Clone,
398		B: 'a + Clone,
399		C: 'a,
400	>(
401		f: impl Fn(C, &A) -> C + 'a,
402		g: impl Fn(C, &B) -> C + 'a,
403		z: C,
404		p: &Apply!(<Brand as Kind!( type Of<'a, A: 'a, B: 'a>: 'a; )>::Of<'a, A, B>),
405	) -> C {
406		Brand::ref_bi_fold_left::<FnBrand, A, B, C>(f, g, z, p)
407	}
408
409	/// Maps elements of both types to a monoid by reference and combines the results.
410	///
411	/// Free function version that dispatches to [the type class' associated function][`RefBifoldable::ref_bi_fold_map`].
412	#[document_signature]
413	///
414	#[document_type_parameters(
415		"The lifetime of the values.",
416		"The brand of the cloneable function to use.",
417		"The brand of the bifoldable structure.",
418		"The type of the first-position elements.",
419		"The type of the second-position elements.",
420		"The monoid type to fold into."
421	)]
422	///
423	#[document_parameters(
424		"The function mapping first-position element references to the monoid.",
425		"The function mapping second-position element references to the monoid.",
426		"The bifoldable structure to fold (borrowed)."
427	)]
428	///
429	#[document_returns("The combined monoid value.")]
430	#[document_examples]
431	///
432	/// ```
433	/// use fp_library::{
434	/// 	brands::*,
435	/// 	functions::explicit::*,
436	/// };
437	///
438	/// let x: Result<i32, i32> = Ok(5);
439	/// let y = bi_fold_map::<RcFnBrand, ResultBrand, _, _, _, _, _>(
440	/// 	(|e: &i32| e.to_string(), |s: &i32| s.to_string()),
441	/// 	&x,
442	/// );
443	/// assert_eq!(y, "5".to_string());
444	/// ```
445	pub fn ref_bi_fold_map<
446		'a,
447		FnBrand: LiftFn + 'a,
448		Brand: RefBifoldable,
449		A: 'a + Clone,
450		B: 'a + Clone,
451		M,
452	>(
453		f: impl Fn(&A) -> M + 'a,
454		g: impl Fn(&B) -> M + 'a,
455		p: &Apply!(<Brand as Kind!( type Of<'a, A: 'a, B: 'a>: 'a; )>::Of<'a, A, B>),
456	) -> M
457	where
458		M: Monoid + 'a, {
459		Brand::ref_bi_fold_map::<FnBrand, A, B, M>(f, g, p)
460	}
461}
462
463pub use inner::*;