Skip to main content

fp_library/classes/
ref_foldable.rs

1//! By-reference variant of [`Foldable`](crate::classes::Foldable).
2//!
3//! **User story:** "I want to fold over a memoized value without consuming it."
4//!
5//! This trait is for types like [`Lazy`](crate::types::Lazy) where the container
6//! holds a cached value accessible by reference. The closures receive `&A` instead
7//! of `A`, avoiding unnecessary cloning.
8//!
9//! ### Examples
10//!
11//! ```
12//! use fp_library::{
13//! 	brands::*,
14//! 	functions::explicit::*,
15//! 	types::*,
16//! };
17//!
18//! let lazy = RcLazy::new(|| 10);
19//! let result =
20//! 	fold_map::<RcFnBrand, LazyBrand<RcLazyConfig>, _, _, _, _>(|a: &i32| a.to_string(), &lazy);
21//! assert_eq!(result, "10");
22//! ```
23
24#[fp_macros::document_module]
25mod inner {
26	use {
27		crate::{
28			classes::*,
29			kinds::*,
30			types::Endofunction,
31		},
32		fp_macros::*,
33	};
34
35	/// By-reference folding over a structure.
36	///
37	/// Similar to [`Foldable`], but closures receive `&A` instead of `A`.
38	/// This is the honest interface for memoized types like [`Lazy`](crate::types::Lazy)
39	/// that internally hold a cached `&A` and would otherwise force a clone
40	/// to satisfy the by-value `Foldable` signature.
41	///
42	/// All three methods (`ref_fold_map`, `ref_fold_right`, `ref_fold_left`)
43	/// have default implementations in terms of each other, so implementors
44	/// only need to provide one.
45	#[kind(type Of<'a, A: 'a>: 'a;)]
46	pub trait RefFoldable {
47		/// Maps values to a monoid by reference and combines them.
48		#[document_signature]
49		///
50		#[document_type_parameters(
51			"The lifetime of the elements.",
52			"The brand of the cloneable function to use.",
53			"The type of the elements in the structure.",
54			"The monoid type."
55		)]
56		///
57		#[document_parameters(
58			"The function to map each element reference to a monoid.",
59			"The structure to fold."
60		)]
61		///
62		#[document_returns("The combined monoid value.")]
63		#[document_examples]
64		///
65		/// ```
66		/// use fp_library::{
67		/// 	brands::*,
68		/// 	functions::explicit::*,
69		/// 	types::*,
70		/// };
71		///
72		/// let lazy = RcLazy::new(|| 5);
73		/// let result =
74		/// 	fold_map::<RcFnBrand, LazyBrand<RcLazyConfig>, _, _, _, _>(|a: &i32| a.to_string(), &lazy);
75		/// assert_eq!(result, "5");
76		/// ```
77		fn ref_fold_map<'a, FnBrand, A: 'a + Clone, M>(
78			func: impl Fn(&A) -> M + 'a,
79			fa: &Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>),
80		) -> M
81		where
82			FnBrand: LiftFn + 'a,
83			M: Monoid + 'a, {
84			Self::ref_fold_right::<FnBrand, A, M>(
85				move |a: &A, acc| Semigroup::append(func(a), acc),
86				Monoid::empty(),
87				fa,
88			)
89		}
90
91		/// Folds the structure from the right by reference.
92		#[document_signature]
93		///
94		#[document_type_parameters(
95			"The lifetime of the elements.",
96			"The brand of the cloneable function to use.",
97			"The type of the elements in the structure.",
98			"The type of the accumulator."
99		)]
100		///
101		#[document_parameters(
102			"The function to apply to each element reference and the accumulator.",
103			"The initial value of the accumulator.",
104			"The structure to fold."
105		)]
106		///
107		#[document_returns("The final accumulator value.")]
108		#[document_examples]
109		///
110		/// ```
111		/// use fp_library::{
112		/// 	brands::*,
113		/// 	functions::explicit::*,
114		/// 	types::*,
115		/// };
116		///
117		/// let lazy = RcLazy::new(|| 10);
118		/// let result =
119		/// 	fold_right::<RcFnBrand, LazyBrand<RcLazyConfig>, _, _, _, _>(|a: &i32, b| *a + b, 5, &lazy);
120		/// assert_eq!(result, 15);
121		/// ```
122		fn ref_fold_right<'a, FnBrand, A: 'a + Clone, B: 'a>(
123			func: impl Fn(&A, B) -> B + 'a,
124			initial: B,
125			fa: &Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>),
126		) -> B
127		where
128			FnBrand: LiftFn + 'a, {
129			let f = <FnBrand as LiftFn>::new(move |(a, b): (A, B)| func(&a, b));
130			let m = Self::ref_fold_map::<FnBrand, A, Endofunction<FnBrand, B>>(
131				move |a: &A| {
132					let a = a.clone();
133					let f = f.clone();
134					Endofunction::<FnBrand, B>::new(<FnBrand as LiftFn>::new(move |b| {
135						f((a.clone(), b))
136					}))
137				},
138				fa,
139			);
140			m.0(initial)
141		}
142
143		/// Folds the structure from the left by reference.
144		#[document_signature]
145		///
146		#[document_type_parameters(
147			"The lifetime of the elements.",
148			"The brand of the cloneable function to use.",
149			"The type of the elements in the structure.",
150			"The type of the accumulator."
151		)]
152		///
153		#[document_parameters(
154			"The function to apply to the accumulator and each element reference.",
155			"The initial value of the accumulator.",
156			"The structure to fold."
157		)]
158		///
159		#[document_returns("The final accumulator value.")]
160		#[document_examples]
161		///
162		/// ```
163		/// use fp_library::{
164		/// 	brands::*,
165		/// 	functions::explicit::*,
166		/// 	types::*,
167		/// };
168		///
169		/// let lazy = RcLazy::new(|| 10);
170		/// let result =
171		/// 	fold_left::<RcFnBrand, LazyBrand<RcLazyConfig>, _, _, _, _>(|b, a: &i32| b + *a, 5, &lazy);
172		/// assert_eq!(result, 15);
173		/// ```
174		fn ref_fold_left<'a, FnBrand, A: 'a + Clone, B: 'a>(
175			func: impl Fn(B, &A) -> B + 'a,
176			initial: B,
177			fa: &Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>),
178		) -> B
179		where
180			FnBrand: LiftFn + 'a, {
181			let f = <FnBrand as LiftFn>::new(move |(b, a): (B, A)| func(b, &a));
182			let m = Self::ref_fold_right::<FnBrand, A, Endofunction<FnBrand, B>>(
183				move |a: &A, k: Endofunction<'a, FnBrand, B>| {
184					let a = a.clone();
185					let f = f.clone();
186					let current =
187						Endofunction::<FnBrand, B>::new(<FnBrand as LiftFn>::new(move |b| {
188							f((b, a.clone()))
189						}));
190					Semigroup::append(k, current)
191				},
192				Endofunction::<FnBrand, B>::empty(),
193				fa,
194			);
195			m.0(initial)
196		}
197	}
198}
199
200pub use inner::*;