Skip to main content

fp_library/classes/
ref_foldable_with_index.rs

1//! By-reference variant of [`FoldableWithIndex`](crate::classes::FoldableWithIndex).
2//!
3//! **User story:** "I want to fold over a memoized value by reference, with access to the index."
4//!
5//! All three methods (`ref_fold_map_with_index`, `ref_fold_right_with_index`,
6//! `ref_fold_left_with_index`) have default implementations in terms of each other,
7//! so implementors only need to provide one.
8//!
9//! ### Examples
10//!
11//! ```
12//! use fp_library::{
13//! 	brands::*,
14//! 	classes::ref_foldable_with_index::RefFoldableWithIndex,
15//! 	types::*,
16//! };
17//!
18//! let lazy = RcLazy::new(|| 42);
19//! let result = <LazyBrand<RcLazyConfig> as RefFoldableWithIndex>::ref_fold_map_with_index::<
20//! 	RcFnBrand,
21//! 	_,
22//! 	_,
23//! >(|_, x: &i32| x.to_string(), &lazy);
24//! assert_eq!(result, "42");
25//! ```
26
27#[fp_macros::document_module]
28mod inner {
29	use {
30		crate::{
31			classes::*,
32			kinds::*,
33			types::{
34				Dual,
35				Endofunction,
36			},
37		},
38		fp_macros::*,
39	};
40
41	/// By-reference folding with index over a structure.
42	///
43	/// Similar to [`FoldableWithIndex`], but the closure receives `&A` instead of `A`.
44	/// This is the honest interface for memoized types like [`Lazy`](crate::types::Lazy)
45	/// that internally hold a cached `&A`.
46	///
47	/// All three methods (`ref_fold_map_with_index`, `ref_fold_right_with_index`,
48	/// `ref_fold_left_with_index`) have default implementations in terms of each other,
49	/// so implementors only need to provide one.
50	#[kind(type Of<'a, A: 'a>: 'a;)]
51	pub trait RefFoldableWithIndex: RefFoldable + WithIndex {
52		/// Maps each element of the structure to a monoid by reference,
53		/// providing the index, and combines the results.
54		#[document_signature]
55		#[document_type_parameters(
56			"The lifetime of the values.",
57			"The brand of the cloneable function to use.",
58			"The type of the elements.",
59			"The monoid type."
60		)]
61		#[document_parameters(
62			"The function to apply to each element's index and reference.",
63			"The structure to fold over."
64		)]
65		#[document_returns("The combined result.")]
66		#[document_examples]
67		///
68		/// ```
69		/// use fp_library::{
70		/// 	brands::*,
71		/// 	classes::ref_foldable_with_index::RefFoldableWithIndex,
72		/// 	types::*,
73		/// };
74		///
75		/// let lazy = RcLazy::new(|| 42);
76		/// let result = <LazyBrand<RcLazyConfig> as RefFoldableWithIndex>::ref_fold_map_with_index::<
77		/// 	RcFnBrand,
78		/// 	_,
79		/// 	_,
80		/// >(|_, x: &i32| x.to_string(), &lazy);
81		/// assert_eq!(result, "42");
82		/// ```
83		fn ref_fold_map_with_index<'a, FnBrand, A: 'a + Clone, R: Monoid + 'a>(
84			f: impl Fn(Self::Index, &A) -> R + 'a,
85			fa: &Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>),
86		) -> R
87		where
88			FnBrand: LiftFn + 'a,
89			Self::Index: 'a, {
90			Self::ref_fold_right_with_index::<FnBrand, A, R>(
91				move |i, a: &A, acc| Semigroup::append(f(i, a), acc),
92				Monoid::empty(),
93				fa,
94			)
95		}
96
97		/// Folds the structure from the right by reference, providing the index.
98		#[document_signature]
99		#[document_type_parameters(
100			"The lifetime of the values.",
101			"The brand of the cloneable function to use.",
102			"The type of the elements.",
103			"The type of the accumulator."
104		)]
105		#[document_parameters(
106			"The function to apply to each element's index, reference, and accumulator.",
107			"The initial value of the accumulator.",
108			"The structure to fold over."
109		)]
110		#[document_returns("The final accumulator value.")]
111		#[document_examples]
112		///
113		/// ```
114		/// use fp_library::{
115		/// 	brands::*,
116		/// 	classes::ref_foldable_with_index::RefFoldableWithIndex,
117		/// 	types::*,
118		/// };
119		///
120		/// let lazy = RcLazy::new(|| 10);
121		/// let result = <LazyBrand<RcLazyConfig> as RefFoldableWithIndex>::ref_fold_right_with_index::<
122		/// 	RcFnBrand,
123		/// 	_,
124		/// 	_,
125		/// >(|_, x: &i32, acc: i32| acc + *x, 0, &lazy);
126		/// assert_eq!(result, 10);
127		/// ```
128		fn ref_fold_right_with_index<'a, FnBrand, A: 'a + Clone, B: 'a>(
129			func: impl Fn(Self::Index, &A, B) -> B + 'a,
130			initial: B,
131			fa: &Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>),
132		) -> B
133		where
134			FnBrand: LiftFn + 'a,
135			Self::Index: 'a, {
136			let f = <FnBrand as LiftFn>::new(move |(i, a, b): (Self::Index, A, B)| func(i, &a, b));
137			let m = Self::ref_fold_map_with_index::<FnBrand, A, Endofunction<FnBrand, B>>(
138				move |i, a: &A| {
139					let a = a.clone();
140					let f = f.clone();
141					Endofunction::<FnBrand, B>::new(<FnBrand as LiftFn>::new(move |b| {
142						let a = a.clone();
143						let i = i.clone();
144						f((i, a, b))
145					}))
146				},
147				fa,
148			);
149			m.0(initial)
150		}
151
152		/// Folds the structure from the left by reference, providing the index.
153		#[document_signature]
154		#[document_type_parameters(
155			"The lifetime of the values.",
156			"The brand of the cloneable function to use.",
157			"The type of the elements.",
158			"The type of the accumulator."
159		)]
160		#[document_parameters(
161			"The function to apply to the accumulator, each element's index, and reference.",
162			"The initial value of the accumulator.",
163			"The structure to fold over."
164		)]
165		#[document_returns("The final accumulator value.")]
166		#[document_examples]
167		///
168		/// ```
169		/// use fp_library::{
170		/// 	brands::*,
171		/// 	classes::ref_foldable_with_index::RefFoldableWithIndex,
172		/// 	types::*,
173		/// };
174		///
175		/// let lazy = RcLazy::new(|| 10);
176		/// let result = <LazyBrand<RcLazyConfig> as RefFoldableWithIndex>::ref_fold_left_with_index::<
177		/// 	RcFnBrand,
178		/// 	_,
179		/// 	_,
180		/// >(|_, acc: i32, x: &i32| acc + *x, 0, &lazy);
181		/// assert_eq!(result, 10);
182		/// ```
183		fn ref_fold_left_with_index<'a, FnBrand, A: 'a + Clone, B: 'a>(
184			func: impl Fn(Self::Index, B, &A) -> B + 'a,
185			initial: B,
186			fa: &Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>),
187		) -> B
188		where
189			FnBrand: LiftFn + 'a,
190			Self::Index: 'a, {
191			let f = <FnBrand as LiftFn>::new(move |(i, b, a): (Self::Index, B, A)| func(i, b, &a));
192			let m = Self::ref_fold_map_with_index::<FnBrand, A, Dual<Endofunction<FnBrand, B>>>(
193				move |i, a: &A| {
194					let a = a.clone();
195					let f = f.clone();
196					Dual(Endofunction::<FnBrand, B>::new(<FnBrand as LiftFn>::new(move |b| {
197						let a = a.clone();
198						let i = i.clone();
199						f((i, b, a))
200					})))
201				},
202				fa,
203			);
204			(m.0).0(initial)
205		}
206	}
207
208	/// Maps each element to a monoid by reference with its index and combines the results.
209	///
210	/// Free function version that dispatches to [the type class' associated function][`RefFoldableWithIndex::ref_fold_map_with_index`].
211	#[document_signature]
212	#[document_type_parameters(
213		"The lifetime of the values.",
214		"The brand of the cloneable function to use.",
215		"The brand of the structure.",
216		"The type of the elements.",
217		"The monoid type."
218	)]
219	#[document_parameters(
220		"The function to apply to each element's index and reference.",
221		"The structure to fold over."
222	)]
223	#[document_returns("The combined result.")]
224	#[document_examples]
225	///
226	/// ```
227	/// use fp_library::{
228	/// 	brands::*,
229	/// 	functions::explicit::*,
230	/// 	types::*,
231	/// };
232	///
233	/// let lazy = RcLazy::new(|| 42);
234	/// let result = fold_map_with_index::<RcFnBrand, LazyBrand<RcLazyConfig>, _, _, _, _>(
235	/// 	|_, x: &i32| x.to_string(),
236	/// 	&lazy,
237	/// );
238	/// assert_eq!(result, "42");
239	/// ```
240	pub fn ref_fold_map_with_index<
241		'a,
242		FnBrand: LiftFn + 'a,
243		Brand: RefFoldableWithIndex,
244		A: 'a + Clone,
245		R: Monoid + 'a,
246	>(
247		f: impl Fn(Brand::Index, &A) -> R + 'a,
248		fa: &Apply!(<Brand as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>),
249	) -> R
250	where
251		Brand::Index: 'a, {
252		Brand::ref_fold_map_with_index::<FnBrand, A, R>(f, fa)
253	}
254}
255
256pub use inner::*;