fp_library/classes/ref_bifoldable.rs
1//! By-reference variant of [`Bifoldable`](crate::classes::Bifoldable).
2//!
3//! **User story:** "I want to fold over a bifoldable value without consuming it."
4//!
5//! This trait is for types where the container holds cached or borrowed values
6//! accessible by reference. The closures receive `&A` and `&B` instead of `A`
7//! and `B`, avoiding unnecessary moves. `A: Clone` and `B: Clone` are required
8//! so that default implementations can own values extracted from references.
9//!
10//! ### Examples
11//!
12//! ```
13//! use fp_library::{
14//! brands::*,
15//! functions::explicit::*,
16//! };
17//!
18//! let x: Result<i32, i32> = Ok(5);
19//! let y = bi_fold_map::<RcFnBrand, ResultBrand, _, _, _, _, _>(
20//! (|e: &i32| e.to_string(), |s: &i32| s.to_string()),
21//! &x,
22//! );
23//! assert_eq!(y, "5".to_string());
24//! ```
25
26#[fp_macros::document_module]
27mod inner {
28 use {
29 crate::{
30 classes::*,
31 kinds::*,
32 types::Endofunction,
33 },
34 fp_macros::*,
35 };
36
37 /// By-reference folding over a bifoldable structure.
38 ///
39 /// Similar to [`Bifoldable`], but closures receive `&A` and `&B` instead of
40 /// owned values. This is the honest interface for types that internally hold
41 /// cached or borrowed values and would otherwise force a clone to satisfy the
42 /// by-value `Bifoldable` signature.
43 ///
44 /// ### Minimal Implementation
45 ///
46 /// A minimal implementation requires either [`RefBifoldable::ref_bi_fold_map`] or
47 /// [`RefBifoldable::ref_bi_fold_right`] to be defined directly:
48 ///
49 /// * If [`RefBifoldable::ref_bi_fold_right`] is implemented,
50 /// [`RefBifoldable::ref_bi_fold_map`] and [`RefBifoldable::ref_bi_fold_left`]
51 /// are derived from it.
52 /// * If [`RefBifoldable::ref_bi_fold_map`] is implemented,
53 /// [`RefBifoldable::ref_bi_fold_right`] is derived via `Endofunction`, and
54 /// [`RefBifoldable::ref_bi_fold_left`] is derived from the derived
55 /// [`RefBifoldable::ref_bi_fold_right`].
56 ///
57 /// Note: defining both defaults creates a circular dependency and will not terminate.
58 ///
59 /// ### Laws
60 ///
61 /// `RefBifoldable` instances must be internally consistent and consistent with
62 /// `Bifunctor` when one is also defined:
63 /// * ref_bi_fold_map/ref_bi_fold_right consistency: `ref_bi_fold_map(f, g, &x) = ref_bi_fold_right(|a, c| append(f(a), c), |b, c| append(g(b), c), empty(), &x)`.
64 #[document_examples]
65 ///
66 /// RefBifoldable laws for [`Result`]:
67 ///
68 /// ```
69 /// use fp_library::{
70 /// brands::*,
71 /// functions::{
72 /// explicit::{
73 /// bi_fold_map,
74 /// bi_fold_right,
75 /// },
76 /// *,
77 /// },
78 /// };
79 ///
80 /// // ResultBrand has Of<E, A> = Result<A, E>, so the first function handles errors
81 /// // and the second function handles ok values.
82 /// let ok: Result<i32, String> = Ok(5);
83 /// let err: Result<i32, String> = Err("err".to_string());
84 /// let f = |e: &String| format!("err:{e}");
85 /// let g = |a: &i32| a.to_string();
86 ///
87 /// // bi_fold_map/bi_fold_right consistency with ref closures (Ok case):
88 /// assert_eq!(
89 /// bi_fold_map::<RcFnBrand, ResultBrand, _, _, _, _, _>((f, g), &ok),
90 /// bi_fold_right::<RcFnBrand, ResultBrand, _, _, _, _, _>(
91 /// (|a: &String, c: String| append(f(a), c), |b: &i32, c: String| append(g(b), c)),
92 /// empty::<String>(),
93 /// &ok,
94 /// ),
95 /// );
96 ///
97 /// // bi_fold_map/bi_fold_right consistency with ref closures (Err case):
98 /// assert_eq!(
99 /// bi_fold_map::<RcFnBrand, ResultBrand, _, _, _, _, _>((f, g), &err),
100 /// bi_fold_right::<RcFnBrand, ResultBrand, _, _, _, _, _>(
101 /// (|a: &String, c: String| append(f(a), c), |b: &i32, c: String| append(g(b), c)),
102 /// empty::<String>(),
103 /// &err,
104 /// ),
105 /// );
106 /// ```
107 #[kind(type Of<'a, A: 'a, B: 'a>: 'a;)]
108 pub trait RefBifoldable {
109 /// Folds the bifoldable structure from right to left by reference using two step functions.
110 ///
111 /// This method performs a right-associative fold, dispatching to `f` for
112 /// elements of the first type and `g` for elements of the second type.
113 /// Closures receive references rather than owned values.
114 #[document_signature]
115 ///
116 #[document_type_parameters(
117 "The lifetime of the values.",
118 "The brand of the cloneable function to use.",
119 "The type of the first-position elements.",
120 "The type of the second-position elements.",
121 "The type of the accumulator."
122 )]
123 ///
124 #[document_parameters(
125 "The step function for first-position element references.",
126 "The step function for second-position element references.",
127 "The initial accumulator value.",
128 "The bifoldable structure to fold (borrowed)."
129 )]
130 ///
131 #[document_returns("The final accumulator value after folding all elements.")]
132 #[document_examples]
133 ///
134 /// ```
135 /// use fp_library::{
136 /// brands::*,
137 /// functions::explicit::*,
138 /// };
139 ///
140 /// let x: Result<i32, i32> = Err(3);
141 /// let y = bi_fold_right::<RcFnBrand, ResultBrand, _, _, _, _, _>(
142 /// (|e: &i32, acc| acc - *e, |s: &i32, acc| acc + *s),
143 /// 10,
144 /// &x,
145 /// );
146 /// assert_eq!(y, 7);
147 /// ```
148 fn ref_bi_fold_right<'a, FnBrand: LiftFn + 'a, A: 'a + Clone, B: 'a + Clone, C: 'a>(
149 f: impl Fn(&A, C) -> C + 'a,
150 g: impl Fn(&B, C) -> C + 'a,
151 z: C,
152 p: &Apply!(<Self as Kind!( type Of<'a, A: 'a, B: 'a>: 'a; )>::Of<'a, A, B>),
153 ) -> C {
154 let f = <FnBrand as LiftFn>::new(move |(a, c): (A, C)| f(&a, c));
155 let g = <FnBrand as LiftFn>::new(move |(b, c): (B, C)| g(&b, c));
156 let endo = Self::ref_bi_fold_map::<FnBrand, A, B, Endofunction<'a, FnBrand, C>>(
157 move |a: &A| {
158 let a = a.clone();
159 let f = f.clone();
160 Endofunction::<FnBrand, C>::new(<FnBrand as LiftFn>::new(move |c| {
161 f((a.clone(), c))
162 }))
163 },
164 move |b: &B| {
165 let b = b.clone();
166 let g = g.clone();
167 Endofunction::<FnBrand, C>::new(<FnBrand as LiftFn>::new(move |c| {
168 g((b.clone(), c))
169 }))
170 },
171 p,
172 );
173 endo.0(z)
174 }
175
176 /// Folds the bifoldable structure from left to right by reference using two step functions.
177 ///
178 /// This method performs a left-associative fold, dispatching to `f` for
179 /// elements of the first type and `g` for elements of the second type.
180 /// Closures receive references rather than owned values.
181 #[document_signature]
182 ///
183 #[document_type_parameters(
184 "The lifetime of the values.",
185 "The brand of the cloneable function to use.",
186 "The type of the first-position elements.",
187 "The type of the second-position elements.",
188 "The type of the accumulator."
189 )]
190 ///
191 #[document_parameters(
192 "The step function for first-position element references.",
193 "The step function for second-position element references.",
194 "The initial accumulator value.",
195 "The bifoldable structure to fold (borrowed)."
196 )]
197 ///
198 #[document_returns("The final accumulator value after folding all elements.")]
199 #[document_examples]
200 ///
201 /// ```
202 /// use fp_library::{
203 /// brands::*,
204 /// functions::explicit::*,
205 /// };
206 ///
207 /// let x: Result<i32, i32> = Ok(5);
208 /// let y = bi_fold_left::<RcFnBrand, ResultBrand, _, _, _, _, _>(
209 /// (|acc, e: &i32| acc - *e, |acc, s: &i32| acc + *s),
210 /// 10,
211 /// &x,
212 /// );
213 /// assert_eq!(y, 15);
214 /// ```
215 fn ref_bi_fold_left<'a, FnBrand: LiftFn + 'a, A: 'a + Clone, B: 'a + Clone, C: 'a>(
216 f: impl Fn(C, &A) -> C + 'a,
217 g: impl Fn(C, &B) -> C + 'a,
218 z: C,
219 p: &Apply!(<Self as Kind!( type Of<'a, A: 'a, B: 'a>: 'a; )>::Of<'a, A, B>),
220 ) -> C {
221 let f = <FnBrand as LiftFn>::new(move |(c, a): (C, A)| f(c, &a));
222 let g = <FnBrand as LiftFn>::new(move |(c, b): (C, B)| g(c, &b));
223 let endo = Self::ref_bi_fold_right::<FnBrand, A, B, Endofunction<'a, FnBrand, C>>(
224 move |a: &A, k: Endofunction<'a, FnBrand, C>| {
225 let a = a.clone();
226 let f = f.clone();
227 let current =
228 Endofunction::<FnBrand, C>::new(<FnBrand as LiftFn>::new(move |c| {
229 f((c, a.clone()))
230 }));
231 Semigroup::append(k, current)
232 },
233 move |b: &B, k: Endofunction<'a, FnBrand, C>| {
234 let b = b.clone();
235 let g = g.clone();
236 let current =
237 Endofunction::<FnBrand, C>::new(<FnBrand as LiftFn>::new(move |c| {
238 g((c, b.clone()))
239 }));
240 Semigroup::append(k, current)
241 },
242 Endofunction::<FnBrand, C>::empty(),
243 p,
244 );
245 endo.0(z)
246 }
247
248 /// Maps elements of both types to a monoid by reference and combines the results.
249 ///
250 /// This method maps each element of the first type using `f` and each element
251 /// of the second type using `g`, receiving references rather than owned values,
252 /// then combines all results using the monoid's `append` operation.
253 #[document_signature]
254 ///
255 #[document_type_parameters(
256 "The lifetime of the values.",
257 "The brand of the cloneable function to use.",
258 "The type of the first-position elements.",
259 "The type of the second-position elements.",
260 "The monoid type to fold into."
261 )]
262 ///
263 #[document_parameters(
264 "The function mapping first-position element references to the monoid.",
265 "The function mapping second-position element references to the monoid.",
266 "The bifoldable structure to fold (borrowed)."
267 )]
268 ///
269 #[document_returns("The combined monoid value.")]
270 #[document_examples]
271 ///
272 /// ```
273 /// use fp_library::{
274 /// brands::*,
275 /// functions::explicit::*,
276 /// };
277 ///
278 /// let x: Result<i32, i32> = Ok(5);
279 /// let y = bi_fold_map::<RcFnBrand, ResultBrand, _, _, _, _, _>(
280 /// (|e: &i32| e.to_string(), |s: &i32| s.to_string()),
281 /// &x,
282 /// );
283 /// assert_eq!(y, "5".to_string());
284 /// ```
285 fn ref_bi_fold_map<'a, FnBrand: LiftFn + 'a, A: 'a + Clone, B: 'a + Clone, M>(
286 f: impl Fn(&A) -> M + 'a,
287 g: impl Fn(&B) -> M + 'a,
288 p: &Apply!(<Self as Kind!( type Of<'a, A: 'a, B: 'a>: 'a; )>::Of<'a, A, B>),
289 ) -> M
290 where
291 M: Monoid + 'a, {
292 Self::ref_bi_fold_right::<FnBrand, A, B, M>(
293 move |a, m| M::append(f(a), m),
294 move |b, m| M::append(g(b), m),
295 M::empty(),
296 p,
297 )
298 }
299 }
300
301 /// Folds the bifoldable structure from right to left by reference using two step functions.
302 ///
303 /// Free function version that dispatches to [the type class' associated function][`RefBifoldable::ref_bi_fold_right`].
304 #[document_signature]
305 ///
306 #[document_type_parameters(
307 "The lifetime of the values.",
308 "The brand of the cloneable function to use.",
309 "The brand of the bifoldable structure.",
310 "The type of the first-position elements.",
311 "The type of the second-position elements.",
312 "The type of the accumulator."
313 )]
314 ///
315 #[document_parameters(
316 "The step function for first-position element references.",
317 "The step function for second-position element references.",
318 "The initial accumulator value.",
319 "The bifoldable structure to fold (borrowed)."
320 )]
321 ///
322 #[document_returns("The final accumulator value after folding all elements.")]
323 #[document_examples]
324 ///
325 /// ```
326 /// use fp_library::{
327 /// brands::*,
328 /// functions::explicit::*,
329 /// };
330 ///
331 /// let x: Result<i32, i32> = Err(3);
332 /// let y = bi_fold_right::<RcFnBrand, ResultBrand, _, _, _, _, _>(
333 /// (|e: &i32, acc| acc - *e, |s: &i32, acc| acc + *s),
334 /// 10,
335 /// &x,
336 /// );
337 /// assert_eq!(y, 7);
338 /// ```
339 pub fn ref_bi_fold_right<
340 'a,
341 FnBrand: LiftFn + 'a,
342 Brand: RefBifoldable,
343 A: 'a + Clone,
344 B: 'a + Clone,
345 C: 'a,
346 >(
347 f: impl Fn(&A, C) -> C + 'a,
348 g: impl Fn(&B, C) -> C + 'a,
349 z: C,
350 p: &Apply!(<Brand as Kind!( type Of<'a, A: 'a, B: 'a>: 'a; )>::Of<'a, A, B>),
351 ) -> C {
352 Brand::ref_bi_fold_right::<FnBrand, A, B, C>(f, g, z, p)
353 }
354
355 /// Folds the bifoldable structure from left to right by reference using two step functions.
356 ///
357 /// Free function version that dispatches to [the type class' associated function][`RefBifoldable::ref_bi_fold_left`].
358 #[document_signature]
359 ///
360 #[document_type_parameters(
361 "The lifetime of the values.",
362 "The brand of the cloneable function to use.",
363 "The brand of the bifoldable structure.",
364 "The type of the first-position elements.",
365 "The type of the second-position elements.",
366 "The type of the accumulator."
367 )]
368 ///
369 #[document_parameters(
370 "The step function for first-position element references.",
371 "The step function for second-position element references.",
372 "The initial accumulator value.",
373 "The bifoldable structure to fold (borrowed)."
374 )]
375 ///
376 #[document_returns("The final accumulator value after folding all elements.")]
377 #[document_examples]
378 ///
379 /// ```
380 /// use fp_library::{
381 /// brands::*,
382 /// functions::explicit::*,
383 /// };
384 ///
385 /// let x: Result<i32, i32> = Ok(5);
386 /// let y = bi_fold_left::<RcFnBrand, ResultBrand, _, _, _, _, _>(
387 /// (|acc, e: &i32| acc - *e, |acc, s: &i32| acc + *s),
388 /// 10,
389 /// &x,
390 /// );
391 /// assert_eq!(y, 15);
392 /// ```
393 pub fn ref_bi_fold_left<
394 'a,
395 FnBrand: LiftFn + 'a,
396 Brand: RefBifoldable,
397 A: 'a + Clone,
398 B: 'a + Clone,
399 C: 'a,
400 >(
401 f: impl Fn(C, &A) -> C + 'a,
402 g: impl Fn(C, &B) -> C + 'a,
403 z: C,
404 p: &Apply!(<Brand as Kind!( type Of<'a, A: 'a, B: 'a>: 'a; )>::Of<'a, A, B>),
405 ) -> C {
406 Brand::ref_bi_fold_left::<FnBrand, A, B, C>(f, g, z, p)
407 }
408
409 /// Maps elements of both types to a monoid by reference and combines the results.
410 ///
411 /// Free function version that dispatches to [the type class' associated function][`RefBifoldable::ref_bi_fold_map`].
412 #[document_signature]
413 ///
414 #[document_type_parameters(
415 "The lifetime of the values.",
416 "The brand of the cloneable function to use.",
417 "The brand of the bifoldable structure.",
418 "The type of the first-position elements.",
419 "The type of the second-position elements.",
420 "The monoid type to fold into."
421 )]
422 ///
423 #[document_parameters(
424 "The function mapping first-position element references to the monoid.",
425 "The function mapping second-position element references to the monoid.",
426 "The bifoldable structure to fold (borrowed)."
427 )]
428 ///
429 #[document_returns("The combined monoid value.")]
430 #[document_examples]
431 ///
432 /// ```
433 /// use fp_library::{
434 /// brands::*,
435 /// functions::explicit::*,
436 /// };
437 ///
438 /// let x: Result<i32, i32> = Ok(5);
439 /// let y = bi_fold_map::<RcFnBrand, ResultBrand, _, _, _, _, _>(
440 /// (|e: &i32| e.to_string(), |s: &i32| s.to_string()),
441 /// &x,
442 /// );
443 /// assert_eq!(y, "5".to_string());
444 /// ```
445 pub fn ref_bi_fold_map<
446 'a,
447 FnBrand: LiftFn + 'a,
448 Brand: RefBifoldable,
449 A: 'a + Clone,
450 B: 'a + Clone,
451 M,
452 >(
453 f: impl Fn(&A) -> M + 'a,
454 g: impl Fn(&B) -> M + 'a,
455 p: &Apply!(<Brand as Kind!( type Of<'a, A: 'a, B: 'a>: 'a; )>::Of<'a, A, B>),
456 ) -> M
457 where
458 M: Monoid + 'a, {
459 Brand::ref_bi_fold_map::<FnBrand, A, B, M>(f, g, p)
460 }
461}
462
463pub use inner::*;