fp_library/classes/foldable.rs
1//! Data structures that can be folded into a single value from the left or right.
2//!
3//! ### Examples
4//!
5//! ```
6//! use fp_library::{
7//! brands::*,
8//! functions::explicit::*,
9//! };
10//!
11//! let x = Some(5);
12//! let y = fold_right::<RcFnBrand, OptionBrand, _, _, _, _>(|a, b| a + b, 10, x);
13//! assert_eq!(y, 15);
14//! ```
15
16#[fp_macros::document_module]
17mod inner {
18 use {
19 crate::{
20 classes::*,
21 kinds::*,
22 types::*,
23 },
24 fp_macros::*,
25 };
26
27 /// A type class for structures that can be folded to a single value.
28 ///
29 /// A `Foldable` represents a structure that can be folded over to combine its elements
30 /// into a single result.
31 ///
32 /// ### Minimal Implementation
33 ///
34 /// A minimal implementation of `Foldable` requires implementing either [`Foldable::fold_right`] or [`Foldable::fold_map`].
35 ///
36 /// * If [`Foldable::fold_right`] is implemented, [`Foldable::fold_map`] and [`Foldable::fold_left`] are derived from it.
37 /// * 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`].
38 ///
39 /// 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.
40 ///
41 /// ### Laws
42 ///
43 /// `Foldable` instances must be internally consistent:
44 /// * fold_map/fold_right consistency: `fold_map(f, fa) = fold_right(|a, m| append(f(a), m), empty(), fa)`.
45 #[document_examples]
46 ///
47 /// Foldable laws for [`Vec`]:
48 ///
49 /// ```
50 /// use fp_library::{
51 /// brands::*,
52 /// functions::{
53 /// explicit::{
54 /// fold_map,
55 /// fold_right,
56 /// },
57 /// *,
58 /// },
59 /// };
60 ///
61 /// let xs = vec![1, 2, 3];
62 /// let f = |a: i32| a.to_string();
63 ///
64 /// // fold_map/fold_right consistency:
65 /// // fold_map(f, fa) = fold_right(|a, m| append(f(a), m), empty(), fa)
66 /// assert_eq!(
67 /// fold_map::<RcFnBrand, VecBrand, _, _, _, _>(f, xs.clone()),
68 /// fold_right::<RcFnBrand, VecBrand, _, _, _, _>(
69 /// |a: i32, m: String| append(f(a), m),
70 /// empty::<String>(),
71 /// xs,
72 /// ),
73 /// );
74 /// ```
75 #[kind(type Of<'a, A: 'a>: 'a;)]
76 pub trait Foldable {
77 /// Folds the structure by applying a function from right to left.
78 ///
79 /// This method performs a right-associative fold of the structure.
80 #[document_signature]
81 ///
82 #[document_type_parameters(
83 "The lifetime of the elements.",
84 "The brand of the cloneable function to use.",
85 "The type of the elements in the structure.",
86 "The type of the accumulator."
87 )]
88 ///
89 #[document_parameters(
90 "The function to apply to each element and the accumulator.",
91 "The initial value of the accumulator.",
92 "The structure to fold."
93 )]
94 ///
95 #[document_returns("The final accumulator value.")]
96 #[document_examples]
97 ///
98 /// ```
99 /// use fp_library::{
100 /// brands::*,
101 /// functions::explicit::*,
102 /// };
103 ///
104 /// let x = Some(5);
105 /// let y = fold_right::<RcFnBrand, OptionBrand, _, _, _, _>(|a, b| a + b, 10, x);
106 /// assert_eq!(y, 15);
107 /// ```
108 fn fold_right<'a, FnBrand, A: 'a + Clone, B: 'a>(
109 func: impl Fn(A, B) -> B + 'a,
110 initial: B,
111 fa: Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>),
112 ) -> B
113 where
114 FnBrand: LiftFn + 'a, {
115 let f = <FnBrand as LiftFn>::new(move |(a, b)| func(a, b));
116 let m = Self::fold_map::<FnBrand, A, Endofunction<FnBrand, B>>(
117 move |a: A| {
118 let f = f.clone();
119 Endofunction::<FnBrand, B>::new(<FnBrand as LiftFn>::new(move |b| {
120 f((a.clone(), b))
121 }))
122 },
123 fa,
124 );
125 m.0(initial)
126 }
127
128 /// Folds the structure by applying a function from left to right.
129 ///
130 /// This method performs a left-associative fold of the structure.
131 #[document_signature]
132 ///
133 #[document_type_parameters(
134 "The lifetime of the elements.",
135 "The brand of the cloneable function to use.",
136 "The type of the elements in the structure.",
137 "The type of the accumulator."
138 )]
139 ///
140 #[document_parameters(
141 "The function to apply to the accumulator and each element.",
142 "The initial value of the accumulator.",
143 "The structure to fold."
144 )]
145 ///
146 #[document_returns("The final accumulator value.")]
147 #[document_examples]
148 ///
149 /// ```
150 /// use fp_library::{
151 /// brands::*,
152 /// functions::explicit::*,
153 /// };
154 ///
155 /// let x = Some(5);
156 /// let y = fold_left::<RcFnBrand, OptionBrand, _, _, _, _>(|b, a| b + a, 10, x);
157 /// assert_eq!(y, 15);
158 /// ```
159 fn fold_left<'a, FnBrand, A: 'a + Clone, B: 'a>(
160 func: impl Fn(B, A) -> B + 'a,
161 initial: B,
162 fa: Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>),
163 ) -> B
164 where
165 FnBrand: LiftFn + 'a, {
166 let f = <FnBrand as LiftFn>::new(move |(b, a)| func(b, a));
167 let m = Self::fold_right::<FnBrand, A, Endofunction<FnBrand, B>>(
168 move |a: A, k: Endofunction<'a, FnBrand, B>| {
169 let f = f.clone();
170 // k is the "rest" of the computation.
171 // We want to perform "current" (f(b, a)) then "rest".
172 // Endofunction composition is f . g (f after g).
173 // So we want k . current.
174 // append(k, current).
175 let current =
176 Endofunction::<FnBrand, B>::new(<FnBrand as LiftFn>::new(move |b| {
177 f((b, a.clone()))
178 }));
179 Semigroup::append(k, current)
180 },
181 Endofunction::<FnBrand, B>::empty(),
182 fa,
183 );
184 m.0(initial)
185 }
186
187 /// Maps values to a monoid and combines them.
188 ///
189 /// This method maps each element of the structure to a monoid and then combines the results using the monoid's `append` operation.
190 #[document_signature]
191 ///
192 #[document_type_parameters(
193 "The lifetime of the elements.",
194 "The brand of the cloneable function to use.",
195 "The type of the elements in the structure.",
196 "The type of the monoid."
197 )]
198 ///
199 #[document_parameters(
200 "The function to map each element to a monoid.",
201 "The structure to fold."
202 )]
203 ///
204 #[document_returns("The combined monoid value.")]
205 #[document_examples]
206 ///
207 /// ```
208 /// use fp_library::{
209 /// brands::*,
210 /// functions::explicit::*,
211 /// };
212 ///
213 /// let x = Some(5);
214 /// let y = fold_map::<RcFnBrand, OptionBrand, _, _, _, _>(|a: i32| a.to_string(), x);
215 /// assert_eq!(y, "5".to_string());
216 /// ```
217 fn fold_map<'a, FnBrand, A: 'a + Clone, M>(
218 func: impl Fn(A) -> M + 'a,
219 fa: Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>),
220 ) -> M
221 where
222 M: Monoid + 'a,
223 FnBrand: LiftFn + 'a, {
224 Self::fold_right::<FnBrand, A, M>(move |a, m| M::append(func(a), m), M::empty(), fa)
225 }
226 }
227}
228
229pub use inner::*;