Skip to main content

fp_library/classes/
foldable_with_index.rs

1//! A `Foldable` with an additional index.
2
3#[fp_macros::document_module]
4mod inner {
5	use {
6		crate::{
7			classes::*,
8			types::Endofunction,
9		},
10		fp_macros::*,
11	};
12
13	/// A `Foldable` with an additional index.
14	///
15	/// A `FoldableWithIndex` is a `Foldable` that also allows you to access the
16	/// index of each element when folding over the structure. The index type is
17	/// uniquely determined by the implementing brand via the [`WithIndex`] supertype.
18	///
19	/// ### Laws
20	///
21	/// `FoldableWithIndex` instances must be compatible with their `Foldable` instance:
22	/// * Compatibility with Foldable: `fold_map(f, fa) = fold_map_with_index(|_, a| f(a), fa)`.
23	#[document_examples]
24	///
25	/// FoldableWithIndex laws for [`Vec`]:
26	///
27	/// ```
28	/// use fp_library::{
29	/// 	brands::*,
30	/// 	classes::foldable_with_index::FoldableWithIndex,
31	/// 	functions::explicit::*,
32	/// };
33	///
34	/// let xs = vec![1, 2, 3];
35	/// let f = |a: i32| a.to_string();
36	///
37	/// // Compatibility with Foldable:
38	/// // fold_map(f, fa) = fold_map_with_index(|_, a| f(a), fa)
39	/// assert_eq!(
40	/// 	fold_map::<RcFnBrand, VecBrand, _, _, _, _>(f, xs.clone()),
41	/// 	VecBrand::fold_map_with_index::<RcFnBrand, _, _>(|_, a| f(a), xs),
42	/// );
43	/// ```
44	pub trait FoldableWithIndex: Foldable + WithIndex {
45		/// Map each element of the structure to a monoid, and combine the results,
46		/// providing the index of each element.
47		///
48		/// Default implementation derives from `fold_right_with_index`.
49		#[document_signature]
50		#[document_type_parameters(
51			"The lifetime of the values.",
52			"The brand of the cloneable function to use.",
53			"The type of the elements.",
54			"The monoid type."
55		)]
56		#[document_parameters(
57			"The function to apply to each element and its index.",
58			"The structure to fold over."
59		)]
60		#[document_returns("The combined result.")]
61		#[document_examples]
62		///
63		/// ```
64		/// use fp_library::{
65		/// 	brands::*,
66		/// 	classes::foldable_with_index::FoldableWithIndex,
67		/// };
68		///
69		/// let result = VecBrand::fold_map_with_index::<RcFnBrand, _, _>(
70		/// 	|i, x: i32| format!("{i}:{x}"),
71		/// 	vec![10, 20, 30],
72		/// );
73		/// assert_eq!(result, "0:101:202:30");
74		/// ```
75		fn fold_map_with_index<'a, FnBrand, A: 'a + Clone, R: Monoid + 'a>(
76			f: impl Fn(Self::Index, A) -> R + 'a,
77			fa: Self::Of<'a, A>,
78		) -> R
79		where
80			FnBrand: LiftFn + 'a,
81			Self::Index: 'a, {
82			Self::fold_right_with_index::<FnBrand, A, R>(
83				move |i, a, acc| Semigroup::append(f(i, a), acc),
84				Monoid::empty(),
85				fa,
86			)
87		}
88
89		/// Folds the structure with index by applying a function from right to left.
90		///
91		/// Default implementation derives from `fold_map_with_index`.
92		#[document_signature]
93		#[document_type_parameters(
94			"The lifetime of the values.",
95			"The brand of the cloneable function to use.",
96			"The type of the elements.",
97			"The type of the accumulator."
98		)]
99		#[document_parameters(
100			"The function to apply to each element's index, the element, and the accumulator.",
101			"The initial accumulator value.",
102			"The structure to fold over."
103		)]
104		#[document_returns("The final accumulator value.")]
105		#[document_examples]
106		///
107		/// ```
108		/// use fp_library::{
109		/// 	brands::*,
110		/// 	classes::foldable_with_index::FoldableWithIndex,
111		/// };
112		///
113		/// let result = VecBrand::fold_right_with_index::<RcFnBrand, _, _>(
114		/// 	|i, x: i32, acc: String| format!("{acc}{i}:{x},"),
115		/// 	String::new(),
116		/// 	vec![10, 20, 30],
117		/// );
118		/// assert_eq!(result, "2:30,1:20,0:10,");
119		/// ```
120		fn fold_right_with_index<'a, FnBrand, A: 'a + Clone, B: 'a>(
121			func: impl Fn(Self::Index, A, B) -> B + 'a,
122			initial: B,
123			fa: Self::Of<'a, A>,
124		) -> B
125		where
126			FnBrand: LiftFn + 'a,
127			Self::Index: 'a, {
128			let f = <FnBrand as LiftFn>::new(move |(i, a, b): (Self::Index, A, B)| func(i, a, b));
129			let m = Self::fold_map_with_index::<FnBrand, A, Endofunction<FnBrand, B>>(
130				move |i: Self::Index, a: A| {
131					let f = f.clone();
132					Endofunction::<FnBrand, B>::new(<FnBrand as LiftFn>::new(move |b| {
133						f((i.clone(), a.clone(), b))
134					}))
135				},
136				fa,
137			);
138			m.0(initial)
139		}
140
141		/// Folds the structure with index by applying a function from left to right.
142		///
143		/// Default implementation derives from `fold_map_with_index`.
144		#[document_signature]
145		#[document_type_parameters(
146			"The lifetime of the values.",
147			"The brand of the cloneable function to use.",
148			"The type of the elements.",
149			"The type of the accumulator."
150		)]
151		#[document_parameters(
152			"The function to apply to the index, the accumulator, and each element.",
153			"The initial accumulator value.",
154			"The structure to fold over."
155		)]
156		#[document_returns("The final accumulator value.")]
157		#[document_examples]
158		///
159		/// ```
160		/// use fp_library::{
161		/// 	brands::*,
162		/// 	classes::foldable_with_index::FoldableWithIndex,
163		/// };
164		///
165		/// let result = VecBrand::fold_left_with_index::<RcFnBrand, _, _>(
166		/// 	|i, acc: String, x: i32| format!("{acc}{i}:{x},"),
167		/// 	String::new(),
168		/// 	vec![10, 20, 30],
169		/// );
170		/// assert_eq!(result, "0:10,1:20,2:30,");
171		/// ```
172		fn fold_left_with_index<'a, FnBrand, A: 'a + Clone, B: 'a>(
173			func: impl Fn(Self::Index, B, A) -> B + 'a,
174			initial: B,
175			fa: Self::Of<'a, A>,
176		) -> B
177		where
178			FnBrand: LiftFn + 'a,
179			Self::Index: 'a, {
180			let f = <FnBrand as LiftFn>::new(move |(i, b, a): (Self::Index, B, A)| func(i, b, a));
181			let m = Self::fold_map_with_index::<
182				FnBrand,
183				A,
184				crate::types::Dual<Endofunction<FnBrand, B>>,
185			>(
186				move |i: Self::Index, a: A| {
187					let f = f.clone();
188					crate::types::Dual(Endofunction::<FnBrand, B>::new(<FnBrand as LiftFn>::new(
189						move |b| f((i.clone(), b, a.clone())),
190					)))
191				},
192				fa,
193			);
194			m.0.0(initial)
195		}
196	}
197
198	/// Maps each element to a monoid with its index and combines the results.
199	///
200	/// Free function version that dispatches to [the type class' associated function][`FoldableWithIndex::fold_map_with_index`].
201	#[document_signature]
202	#[document_type_parameters(
203		"The lifetime of the values.",
204		"The brand of the cloneable function to use.",
205		"The brand of the structure.",
206		"The type of the elements.",
207		"The monoid type."
208	)]
209	#[document_parameters(
210		"The function to apply to each element and its index.",
211		"The structure to fold over."
212	)]
213	#[document_returns("The combined result.")]
214	#[document_examples]
215	///
216	/// ```
217	/// use fp_library::{
218	/// 	brands::*,
219	/// 	functions::explicit::*,
220	/// };
221	///
222	/// let result = fold_map_with_index::<RcFnBrand, VecBrand, _, _, _, _>(
223	/// 	|i, x: i32| format!("{i}:{x}"),
224	/// 	vec![10, 20, 30],
225	/// );
226	/// assert_eq!(result, "0:101:202:30");
227	/// ```
228	pub fn fold_map_with_index<
229		'a,
230		FnBrand: LiftFn + 'a,
231		Brand: FoldableWithIndex,
232		A: 'a + Clone,
233		R: Monoid + 'a,
234	>(
235		f: impl Fn(Brand::Index, A) -> R + 'a,
236		fa: Brand::Of<'a, A>,
237	) -> R
238	where
239		Brand::Index: 'a, {
240		Brand::fold_map_with_index::<FnBrand, A, R>(f, fa)
241	}
242
243	/// Folds the structure with index by applying a function from right to left.
244	///
245	/// Free function version that dispatches to [the type class' associated function][`FoldableWithIndex::fold_right_with_index`].
246	#[document_signature]
247	#[document_type_parameters(
248		"The lifetime of the values.",
249		"The brand of the cloneable function to use.",
250		"The brand of the structure.",
251		"The type of the elements.",
252		"The type of the accumulator."
253	)]
254	#[document_parameters(
255		"The function to apply.",
256		"The initial accumulator value.",
257		"The structure to fold over."
258	)]
259	#[document_returns("The final accumulator value.")]
260	#[document_examples]
261	///
262	/// ```
263	/// use fp_library::{
264	/// 	brands::*,
265	/// 	functions::explicit::*,
266	/// };
267	///
268	/// let result = fold_right_with_index::<RcFnBrand, VecBrand, _, _, _, _>(
269	/// 	|i, x: i32, acc: String| format!("{acc}{i}:{x},"),
270	/// 	String::new(),
271	/// 	vec![10, 20, 30],
272	/// );
273	/// assert_eq!(result, "2:30,1:20,0:10,");
274	/// ```
275	pub fn fold_right_with_index<
276		'a,
277		FnBrand: LiftFn + 'a,
278		Brand: FoldableWithIndex,
279		A: 'a + Clone,
280		B: 'a,
281	>(
282		func: impl Fn(Brand::Index, A, B) -> B + 'a,
283		initial: B,
284		fa: Brand::Of<'a, A>,
285	) -> B
286	where
287		Brand::Index: 'a, {
288		Brand::fold_right_with_index::<FnBrand, A, B>(func, initial, fa)
289	}
290
291	/// Folds the structure with index by applying a function from left to right.
292	///
293	/// Free function version that dispatches to [the type class' associated function][`FoldableWithIndex::fold_left_with_index`].
294	#[document_signature]
295	#[document_type_parameters(
296		"The lifetime of the values.",
297		"The brand of the cloneable function to use.",
298		"The brand of the structure.",
299		"The type of the elements.",
300		"The type of the accumulator."
301	)]
302	#[document_parameters(
303		"The function to apply.",
304		"The initial accumulator value.",
305		"The structure to fold over."
306	)]
307	#[document_returns("The final accumulator value.")]
308	#[document_examples]
309	///
310	/// ```
311	/// use fp_library::{
312	/// 	brands::*,
313	/// 	functions::explicit::*,
314	/// };
315	///
316	/// let result = fold_left_with_index::<RcFnBrand, VecBrand, _, _, _, _>(
317	/// 	|i, acc: String, x: i32| format!("{acc}{i}:{x},"),
318	/// 	String::new(),
319	/// 	vec![10, 20, 30],
320	/// );
321	/// assert_eq!(result, "0:10,1:20,2:30,");
322	/// ```
323	pub fn fold_left_with_index<
324		'a,
325		FnBrand: LiftFn + 'a,
326		Brand: FoldableWithIndex,
327		A: 'a + Clone,
328		B: 'a,
329	>(
330		func: impl Fn(Brand::Index, B, A) -> B + 'a,
331		initial: B,
332		fa: Brand::Of<'a, A>,
333	) -> B
334	where
335		Brand::Index: 'a, {
336		Brand::fold_left_with_index::<FnBrand, A, B>(func, initial, fa)
337	}
338}
339
340pub use inner::*;