fp_library/classes/foldable.rs
1use super::monoid::Monoid;
2use crate::{
3 brands::RcFnBrand,
4 classes::{clonable_fn::ClonableFn, semigroup::Semigroup},
5 hkt::{Apply1L1T, Kind1L1T},
6 types::Endofunction,
7};
8use std::rc::Rc;
9
10/// A type class for structures that can be folded to a single value.
11///
12/// A `Foldable` represents a structure that can be folded over to combine its elements
13/// into a single result.
14///
15/// # Minimal Implementation
16///
17/// A minimal implementation of `Foldable` requires implementing either [`Foldable::fold_right`] or [`Foldable::fold_map`].
18///
19/// * If [`Foldable::fold_right`] is implemented, [`Foldable::fold_map`] and [`Foldable::fold_left`] are derived from it.
20/// * If [`Foldable::fold_map`] is implemented, [`Foldable::fold_right`] is derived from it, and [`Foldable::fold_left`] is derived from the derived [`Foldable::fold_right`].
21///
22/// Note that [`Foldable::fold_left`] is not sufficient on its own because the default implementations of [`Foldable::fold_right`] and [`Foldable::fold_map`] do not depend on it.
23pub trait Foldable: Kind1L1T {
24 /// Folds the structure by applying a function from right to left.
25 ///
26 /// # Type Signature
27 ///
28 /// `forall a b. Foldable t => ((a, b) -> b, b, t a) -> b`
29 ///
30 /// # Parameters
31 ///
32 /// * `f`: The function to apply to each element and the accumulator.
33 /// * `init`: The initial value of the accumulator.
34 /// * `fa`: The structure to fold.
35 ///
36 /// # Returns
37 ///
38 /// The final accumulator value.
39 ///
40 /// # Examples
41 ///
42 /// ```
43 /// use fp_library::classes::foldable::Foldable;
44 /// use fp_library::brands::OptionBrand;
45 ///
46 /// let x = Some(5);
47 /// let y = OptionBrand::fold_right(|a, b| a + b, 10, x);
48 /// assert_eq!(y, 15);
49 /// ```
50 fn fold_right<'a, A: 'a + Clone, B: 'a, F>(
51 f: F,
52 init: B,
53 fa: Apply1L1T<'a, Self, A>,
54 ) -> B
55 where
56 F: Fn(A, B) -> B + 'a,
57 {
58 let f = Rc::new(f);
59 let m = Self::fold_map(
60 move |a: A| {
61 let f = f.clone();
62 Endofunction::<RcFnBrand, B>::new(<RcFnBrand as ClonableFn>::new(move |b| {
63 f(a.clone(), b)
64 }))
65 },
66 fa,
67 );
68 m.0(init)
69 }
70
71 /// Folds the structure by applying a function from left to right.
72 ///
73 /// # Type Signature
74 ///
75 /// `forall a b. Foldable t => ((b, a) -> b, b, t a) -> b`
76 ///
77 /// # Parameters
78 ///
79 /// * `f`: The function to apply to the accumulator and each element.
80 /// * `init`: The initial value of the accumulator.
81 /// * `fa`: The structure to fold.
82 ///
83 /// # Returns
84 ///
85 /// The final accumulator value.
86 ///
87 /// # Examples
88 ///
89 /// ```
90 /// use fp_library::classes::foldable::Foldable;
91 /// use fp_library::brands::OptionBrand;
92 ///
93 /// let x = Some(5);
94 /// let y = OptionBrand::fold_left(|b, a| b + a, 10, x);
95 /// assert_eq!(y, 15);
96 /// ```
97 fn fold_left<'a, A: 'a + Clone, B: 'a, F>(
98 f: F,
99 init: B,
100 fa: Apply1L1T<'a, Self, A>,
101 ) -> B
102 where
103 F: Fn(B, A) -> B + 'a,
104 {
105 let f = Rc::new(f);
106 let m = Self::fold_right(
107 move |a: A, k: Endofunction<'a, RcFnBrand, B>| {
108 let f = f.clone();
109 // k is the "rest" of the computation.
110 // We want to perform "current" (f(b, a)) then "rest".
111 // Endofunction composition is f . g (f after g).
112 // So we want k . current.
113 // append(k, current).
114 let current =
115 Endofunction::<RcFnBrand, B>::new(<RcFnBrand as ClonableFn>::new(move |b| {
116 f(b, a.clone())
117 }));
118 Semigroup::append(k, current)
119 },
120 Endofunction::<RcFnBrand, B>::empty(),
121 fa,
122 );
123 m.0(init)
124 }
125
126 /// Maps values to a monoid and combines them.
127 ///
128 /// # Type Signature
129 ///
130 /// `forall a m. (Foldable t, Monoid m) => ((a) -> m, t a) -> m`
131 ///
132 /// # Parameters
133 ///
134 /// * `f`: The function to map each element to a monoid.
135 /// * `fa`: The structure to fold.
136 ///
137 /// # Returns
138 ///
139 /// The combined monoid value.
140 ///
141 /// # Examples
142 ///
143 /// ```
144 /// use fp_library::classes::foldable::Foldable;
145 /// use fp_library::brands::OptionBrand;
146 /// use fp_library::types::string; // Import Monoid impl for String
147 ///
148 /// let x = Some(5);
149 /// let y = OptionBrand::fold_map(|a: i32| a.to_string(), x);
150 /// assert_eq!(y, "5".to_string());
151 /// ```
152 fn fold_map<'a, A: 'a + Clone, M, F>(
153 f: F,
154 fa: Apply1L1T<'a, Self, A>,
155 ) -> M
156 where
157 M: Monoid + 'a,
158 F: Fn(A) -> M + 'a,
159 {
160 Self::fold_right(move |a, m| M::append(f(a), m), M::empty(), fa)
161 }
162}
163
164/// Folds the structure by applying a function from right to left.
165///
166/// Free function version that dispatches to [the type class' associated function][`Foldable::fold_right`].
167///
168/// # Type Signature
169///
170/// `forall a b. Foldable t => ((a, b) -> b, b, t a) -> b`
171///
172/// # Parameters
173///
174/// * `f`: The function to apply to each element and the accumulator.
175/// * `init`: The initial value of the accumulator.
176/// * `fa`: The structure to fold.
177///
178/// # Returns
179///
180/// The final accumulator value.
181///
182/// # Examples
183///
184/// ```
185/// use fp_library::classes::foldable::fold_right;
186/// use fp_library::brands::OptionBrand;
187///
188/// let x = Some(5);
189/// let y = fold_right::<OptionBrand, _, _, _>(|a, b| a + b, 10, x);
190/// assert_eq!(y, 15);
191/// ```
192pub fn fold_right<'a, Brand: Foldable, A: 'a + Clone, B: 'a, F>(
193 f: F,
194 init: B,
195 fa: Apply1L1T<'a, Brand, A>,
196) -> B
197where
198 F: Fn(A, B) -> B + 'a,
199{
200 Brand::fold_right(f, init, fa)
201}
202
203/// Folds the structure by applying a function from left to right.
204///
205/// Free function version that dispatches to [the type class' associated function][`Foldable::fold_left`].
206///
207/// # Type Signature
208///
209/// `forall a b. Foldable t => ((b, a) -> b, b, t a) -> b`
210///
211/// # Parameters
212///
213/// * `f`: The function to apply to the accumulator and each element.
214/// * `init`: The initial value of the accumulator.
215/// * `fa`: The structure to fold.
216///
217/// # Returns
218///
219/// The final accumulator value.
220///
221/// # Examples
222///
223/// ```
224/// use fp_library::classes::foldable::fold_left;
225/// use fp_library::brands::OptionBrand;
226///
227/// let x = Some(5);
228/// let y = fold_left::<OptionBrand, _, _, _>(|b, a| b + a, 10, x);
229/// assert_eq!(y, 15);
230/// ```
231pub fn fold_left<'a, Brand: Foldable, A: 'a + Clone, B: 'a, F>(
232 f: F,
233 init: B,
234 fa: Apply1L1T<'a, Brand, A>,
235) -> B
236where
237 F: Fn(B, A) -> B + 'a,
238{
239 Brand::fold_left(f, init, fa)
240}
241
242/// Maps values to a monoid and combines them.
243///
244/// Free function version that dispatches to [the type class' associated function][`Foldable::fold_map`].
245///
246/// # Type Signature
247///
248/// `forall a m. (Foldable t, Monoid m) => ((a) -> m, t a) -> m`
249///
250/// # Parameters
251///
252/// * `f`: The function to map each element to a monoid.
253/// * `fa`: The structure to fold.
254///
255/// # Returns
256///
257/// The combined monoid value.
258///
259/// # Examples
260///
261/// ```
262/// use fp_library::classes::foldable::fold_map;
263/// use fp_library::brands::OptionBrand;
264/// use fp_library::types::string; // Import Monoid impl for String
265///
266/// let x = Some(5);
267/// let y = fold_map::<OptionBrand, _, _, _>(|a: i32| a.to_string(), x);
268/// assert_eq!(y, "5".to_string());
269/// ```
270pub fn fold_map<'a, Brand: Foldable, A: 'a + Clone, M, F>(
271 f: F,
272 fa: Apply1L1T<'a, Brand, A>,
273) -> M
274where
275 M: Monoid + 'a,
276 F: Fn(A) -> M + 'a,
277{
278 Brand::fold_map(f, fa)
279}