fp_library/types/thunk.rs
1//! Deferred, non-memoized computation with higher-kinded type support.
2//!
3//! Builds computation chains without stack safety guarantees but supports borrowing and lifetime polymorphism. Each call to [`Thunk::evaluate`] re-executes the computation. For stack-safe alternatives, use [`Trampoline`](crate::types::Trampoline).
4
5#[fp_macros::document_module]
6mod inner {
7 use crate::{
8 Apply,
9 brands::ThunkBrand,
10 classes::{
11 ApplyFirst, ApplySecond, CloneableFn, Deferrable, Evaluable, Foldable, Functor, Lift,
12 MonadRec, Monoid, Pointed, Semiapplicative, Semigroup, Semimonad,
13 },
14 impl_kind,
15 kinds::*,
16 types::{Lazy, LazyConfig, Step},
17 };
18 use fp_macros::{document_fields, document_parameters, document_type_parameters};
19
20 /// A deferred computation that produces a value of type `A`.
21 ///
22 /// `Thunk` is NOT memoized - each call to [`Thunk::evaluate`] re-executes the computation.
23 /// This type exists to build computation chains without allocation overhead.
24 ///
25 /// Unlike [`Trampoline`](crate::types::Trampoline), `Thunk` does NOT require `'static` and CAN implement
26 /// HKT traits like [`Functor`], [`Semimonad`], etc.
27 ///
28 /// ### Higher-Kinded Type Representation
29 ///
30 /// The higher-kinded representation of this type constructor is [`ThunkBrand`](crate::brands::ThunkBrand),
31 /// which is fully polymorphic over the result type.
32 ///
33 /// ### Trade-offs vs `Trampoline`
34 ///
35 /// | Aspect | `Thunk<'a, A>` | `Trampoline<A>` |
36 /// |----------------|-----------------------------|----------------------------|
37 /// | HKT compatible | ✅ Yes | ❌ No (requires `'static`) |
38 /// | Stack-safe | ⚠️ Partial (tail_rec_m only) | ✅ Yes (unlimited) |
39 /// | Lifetime | `'a` (can borrow) | `'static` only |
40 /// | Use case | Glue code, composition | Deep recursion, pipelines |
41 ///
42 /// ### Algebraic Properties
43 ///
44 /// `Thunk` is a proper Monad:
45 /// - `pure(a).evaluate() == a` (left identity).
46 /// - `thunk.bind(pure) == thunk` (right identity).
47 /// - `thunk.bind(f).bind(g) == thunk.bind(|a| f(a).bind(g))` (associativity).
48 ///
49 /// ### Limitations
50 ///
51 /// **Cannot implement `Traversable`**: `Thunk` wraps `Box<dyn FnOnce() -> A>`, which cannot be cloned
52 /// because `FnOnce` is consumed when called. The [`Traversable`](crate::classes::Traversable) trait
53 /// requires `Clone` bounds on the result type (to build the output structure), making it fundamentally
54 /// incompatible with `Thunk`'s design. This is an intentional trade-off: `Thunk` prioritizes
55 /// zero-overhead deferred execution and lifetime flexibility over structural cloning.
56 ///
57 /// Implemented typeclasses:
58 /// - ✅ [`Functor`], [`Foldable`], [`Semimonad`]/Monad, [`Semiapplicative`]/Applicative
59 /// - ❌ [`Traversable`](crate::classes::Traversable) (requires `Clone`)
60 ///
61 /// ### Type Parameters
62 ///
63 #[document_type_parameters(
64 "The lifetime of the computation.",
65 "The type of the value produced by the computation."
66 )]
67 ///
68 /// ### Fields
69 ///
70 #[document_fields("The closure that performs the computation.")]
71 ///
72 /// ### Examples
73 ///
74 /// ```
75 /// use fp_library::types::*;
76 ///
77 /// let computation = Thunk::new(|| 5)
78 /// .map(|x| x * 2)
79 /// .map(|x| x + 1);
80 ///
81 /// // No computation has happened yet!
82 /// // Only when we call evaluate() does it execute:
83 /// let result = computation.evaluate();
84 /// assert_eq!(result, 11);
85 /// ```
86 pub struct Thunk<'a, A>(Box<dyn FnOnce() -> A + 'a>);
87
88 /// ### Type Parameters
89 ///
90 #[document_type_parameters(
91 "The lifetime of the computation.",
92 "The type of the value produced by the computation."
93 )]
94 #[document_parameters("The thunk instance.")]
95 impl<'a, A: 'a> Thunk<'a, A> {
96 /// Creates a new `Thunk` from a thunk.
97 ///
98 /// ### Type Signature
99 ///
100 #[document_signature]
101 ///
102 /// ### Type Parameters
103 ///
104 #[document_type_parameters("The type of the closure.")]
105 ///
106 /// ### Parameters
107 ///
108 #[document_parameters("The thunk to wrap.")]
109 ///
110 /// ### Returns
111 ///
112 /// A new `Thunk` instance.
113 ///
114 /// ### Examples
115 ///
116 /// ```
117 /// use fp_library::types::*;
118 ///
119 /// let thunk = Thunk::new(|| 42);
120 /// assert_eq!(thunk.evaluate(), 42);
121 /// ```
122 pub fn new<F>(f: F) -> Self
123 where
124 F: FnOnce() -> A + 'a,
125 {
126 Thunk(Box::new(f))
127 }
128
129 /// Returns a pure value (already computed).
130 ///
131 /// ### Type Signature
132 ///
133 #[document_signature]
134 ///
135 /// ### Parameters
136 ///
137 #[document_parameters("The value to wrap.")]
138 ///
139 /// ### Returns
140 ///
141 /// A new `Thunk` instance containing the value.
142 ///
143 /// ### Examples
144 ///
145 /// ```
146 /// use fp_library::{brands::*, functions::*, classes::*};
147 ///
148 /// let thunk = pure::<ThunkBrand, _>(42);
149 /// assert_eq!(thunk.evaluate(), 42);
150 /// ```
151 pub fn pure(a: A) -> Self
152 where
153 A: 'a,
154 {
155 Thunk::new(move || a)
156 }
157
158 /// Defers a computation that returns a Thunk.
159 ///
160 /// ### Type Signature
161 ///
162 #[document_signature]
163 ///
164 /// ### Type Parameters
165 ///
166 #[document_type_parameters("The type of the closure.")]
167 ///
168 /// ### Parameters
169 ///
170 #[document_parameters("The thunk that returns a `Thunk`.")]
171 ///
172 /// ### Returns
173 ///
174 /// A new `Thunk` instance.
175 ///
176 /// ### Examples
177 ///
178 /// ```
179 /// use fp_library::{brands::*, functions::*, types::*};
180 ///
181 /// let thunk = Thunk::defer(|| pure::<ThunkBrand, _>(42));
182 /// assert_eq!(thunk.evaluate(), 42);
183 /// ```
184 pub fn defer<F>(f: F) -> Self
185 where
186 F: FnOnce() -> Thunk<'a, A> + 'a,
187 {
188 Thunk::new(move || f().evaluate())
189 }
190
191 /// Monadic bind: chains computations.
192 ///
193 /// Note: Each `bind` adds to the call stack. For deep recursion,
194 /// use [`Trampoline`](crate::types::Trampoline) instead.
195 ///
196 /// ### Type Signature
197 ///
198 #[document_signature]
199 ///
200 /// ### Type Parameters
201 ///
202 #[document_type_parameters(
203 "The type of the result of the new computation.",
204 "The type of the function to apply."
205 )]
206 ///
207 /// ### Parameters
208 ///
209 #[document_parameters("The function to apply to the result of the computation.")]
210 ///
211 /// ### Returns
212 ///
213 /// A new `Thunk` instance representing the chained computation.
214 ///
215 /// ### Examples
216 ///
217 /// ```
218 /// use fp_library::{brands::*, functions::*};
219 ///
220 /// let thunk = pure::<ThunkBrand, _>(21).bind(|x| pure::<ThunkBrand, _>(x * 2));
221 /// assert_eq!(thunk.evaluate(), 42);
222 /// ```
223 pub fn bind<B: 'a, F>(
224 self,
225 f: F,
226 ) -> Thunk<'a, B>
227 where
228 F: FnOnce(A) -> Thunk<'a, B> + 'a,
229 {
230 Thunk::new(move || {
231 let a = (self.0)();
232 let thunk_b = f(a);
233 (thunk_b.0)()
234 })
235 }
236
237 /// Functor map: transforms the result.
238 ///
239 /// ### Type Signature
240 ///
241 #[document_signature]
242 ///
243 /// ### Type Parameters
244 ///
245 #[document_type_parameters(
246 "The type of the result of the transformation.",
247 "The type of the transformation function."
248 )]
249 ///
250 /// ### Parameters
251 ///
252 #[document_parameters("The function to apply to the result of the computation.")]
253 ///
254 /// ### Returns
255 ///
256 /// A new `Thunk` instance with the transformed result.
257 ///
258 /// ### Examples
259 ///
260 /// ```
261 /// use fp_library::{brands::*, functions::*};
262 ///
263 /// let thunk = pure::<ThunkBrand, _>(21).map(|x| x * 2);
264 /// assert_eq!(thunk.evaluate(), 42);
265 /// ```
266 pub fn map<B: 'a, F>(
267 self,
268 f: F,
269 ) -> Thunk<'a, B>
270 where
271 F: FnOnce(A) -> B + 'a,
272 {
273 Thunk::new(move || f((self.0)()))
274 }
275
276 /// Forces evaluation and returns the result.
277 ///
278 /// ### Type Signature
279 ///
280 #[document_signature]
281 ///
282 /// ### Returns
283 ///
284 /// The result of the computation.
285 ///
286 /// ### Examples
287 ///
288 /// ```
289 /// use fp_library::{brands::*, functions::*};
290 ///
291 /// let thunk = pure::<ThunkBrand, _>(42);
292 /// assert_eq!(thunk.evaluate(), 42);
293 /// ```
294 pub fn evaluate(self) -> A {
295 (self.0)()
296 }
297 }
298
299 /// ### Type Parameters
300 ///
301 #[document_type_parameters(
302 "The lifetime of the computation.",
303 "The type of the value produced by the computation.",
304 "The memoization configuration."
305 )]
306 impl<'a, A, Config> From<Lazy<'a, A, Config>> for Thunk<'a, A>
307 where
308 A: Clone + 'a,
309 Config: LazyConfig,
310 {
311 /// ### Type Signature
312 ///
313 #[document_signature]
314 ///
315 /// ### Parameters
316 ///
317 #[document_parameters("The lazy value to convert.")]
318 fn from(lazy: Lazy<'a, A, Config>) -> Self {
319 Thunk::new(move || lazy.evaluate().clone())
320 }
321 }
322
323 impl_kind! {
324 for ThunkBrand {
325 type Of<'a, A: 'a>: 'a = Thunk<'a, A>;
326 }
327 }
328
329 /// ### Type Parameters
330 ///
331 #[document_type_parameters(
332 "The lifetime of the computation.",
333 "The type of the value produced by the computation."
334 )]
335 impl<'a, A: 'a> Deferrable<'a> for Thunk<'a, A> {
336 /// Creates a `Thunk` from a computation that produces it.
337 ///
338 /// ### Type Signature
339 ///
340 #[document_signature]
341 ///
342 /// ### Type Parameters
343 ///
344 #[document_type_parameters("The type of the closure.")]
345 ///
346 /// ### Parameters
347 ///
348 #[document_parameters("A thunk that produces the thunk.")]
349 ///
350 /// ### Returns
351 ///
352 /// The deferred thunk.
353 ///
354 /// ### Examples
355 ///
356 /// ```
357 /// use fp_library::{brands::*, functions::*, types::*, classes::Deferrable};
358 ///
359 /// let task: Thunk<i32> = Deferrable::defer(|| Thunk::pure(42));
360 /// assert_eq!(task.evaluate(), 42);
361 /// ```
362 fn defer<F>(f: F) -> Self
363 where
364 F: FnOnce() -> Self + 'a,
365 Self: Sized,
366 {
367 Thunk::defer(f)
368 }
369 }
370
371 impl Functor for ThunkBrand {
372 /// Maps a function over the result of a `Thunk` computation.
373 ///
374 /// ### Type Signature
375 ///
376 #[document_signature]
377 ///
378 /// ### Type Parameters
379 ///
380 #[document_type_parameters(
381 "The lifetime of the computation.",
382 "The type of the value inside the `Thunk`.",
383 "The type of the result of the transformation.",
384 "The type of the transformation function."
385 )]
386 ///
387 /// ### Parameters
388 ///
389 #[document_parameters(
390 "The function to apply to the result of the computation.",
391 "The `Thunk` instance."
392 )]
393 ///
394 /// ### Returns
395 ///
396 /// A new `Thunk` instance with the transformed result.
397 ///
398 /// ### Examples
399 ///
400 /// ```
401 /// use fp_library::{brands::*, functions::*};
402 ///
403 /// let thunk = pure::<ThunkBrand, _>(10);
404 /// let mapped = map::<ThunkBrand, _, _, _>(|x| x * 2, thunk);
405 /// assert_eq!(mapped.evaluate(), 20);
406 /// ```
407 fn map<'a, A: 'a, B: 'a, Func>(
408 func: Func,
409 fa: Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>),
410 ) -> Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, B>)
411 where
412 Func: Fn(A) -> B + 'a,
413 {
414 fa.map(func)
415 }
416 }
417
418 impl Pointed for ThunkBrand {
419 /// Wraps a value in a `Thunk` context.
420 ///
421 /// ### Type Signature
422 ///
423 #[document_signature]
424 ///
425 /// ### Type Parameters
426 ///
427 #[document_type_parameters(
428 "The lifetime of the computation.",
429 "The type of the value to wrap."
430 )]
431 ///
432 /// ### Parameters
433 ///
434 #[document_parameters("The value to wrap.")]
435 ///
436 /// ### Returns
437 ///
438 /// A new `Thunk` instance containing the value.
439 ///
440 /// ### Examples
441 ///
442 /// ```
443 /// use fp_library::{brands::*, functions::*, types::*};
444 ///
445 /// let thunk: Thunk<i32> = pure::<ThunkBrand, _>(42);
446 /// assert_eq!(thunk.evaluate(), 42);
447 /// ```
448 fn pure<'a, A: 'a>(a: A) -> Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>) {
449 Thunk::pure(a)
450 }
451 }
452
453 impl Lift for ThunkBrand {
454 /// Lifts a binary function into the `Thunk` context.
455 ///
456 /// ### Type Signature
457 ///
458 #[document_signature]
459 ///
460 /// ### Type Parameters
461 ///
462 #[document_type_parameters(
463 "The lifetime of the computation.",
464 "The type of the first value.",
465 "The type of the second value.",
466 "The type of the result.",
467 "The type of the binary function."
468 )]
469 ///
470 /// ### Parameters
471 ///
472 #[document_parameters(
473 "The binary function to apply.",
474 "The first `Thunk`.",
475 "The second `Thunk`."
476 )]
477 ///
478 /// ### Returns
479 ///
480 /// A new `Thunk` instance containing the result of applying the function.
481 ///
482 /// ### Examples
483 ///
484 /// ```
485 /// use fp_library::{brands::*, functions::*};
486 ///
487 /// let eval1 = pure::<ThunkBrand, _>(10);
488 /// let eval2 = pure::<ThunkBrand, _>(20);
489 /// let result = lift2::<ThunkBrand, _, _, _, _>(|a, b| a + b, eval1, eval2);
490 /// assert_eq!(result.evaluate(), 30);
491 /// ```
492 fn lift2<'a, A, B, C, Func>(
493 func: Func,
494 fa: Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>),
495 fb: Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, B>),
496 ) -> Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, C>)
497 where
498 Func: Fn(A, B) -> C + 'a,
499 A: Clone + 'a,
500 B: Clone + 'a,
501 C: 'a,
502 {
503 fa.bind(move |a| fb.map(move |b| func(a, b)))
504 }
505 }
506
507 impl ApplyFirst for ThunkBrand {}
508 impl ApplySecond for ThunkBrand {}
509
510 impl Semiapplicative for ThunkBrand {
511 /// Applies a function wrapped in `Thunk` to a value wrapped in `Thunk`.
512 ///
513 /// ### Type Signature
514 ///
515 #[document_signature]
516 ///
517 /// ### Type Parameters
518 ///
519 #[document_type_parameters(
520 "The lifetime of the computation.",
521 "The brand of the cloneable function wrapper.",
522 "The type of the input.",
523 "The type of the result."
524 )]
525 ///
526 /// ### Parameters
527 ///
528 #[document_parameters(
529 "The `Thunk` containing the function.",
530 "The `Thunk` containing the value."
531 )]
532 ///
533 /// ### Returns
534 ///
535 /// A new `Thunk` instance containing the result of applying the function.
536 ///
537 /// ### Examples
538 ///
539 /// ```
540 /// use fp_library::{brands::*, functions::*};
541 ///
542 /// let func = pure::<ThunkBrand, _>(cloneable_fn_new::<RcFnBrand, _, _>(|x: i32| x * 2));
543 /// let val = pure::<ThunkBrand, _>(21);
544 /// let result = apply::<RcFnBrand, ThunkBrand, _, _>(func, val);
545 /// assert_eq!(result.evaluate(), 42);
546 /// ```
547 fn apply<'a, FnBrand: 'a + CloneableFn, A: 'a + Clone, B: 'a>(
548 ff: Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, <FnBrand as CloneableFn>::Of<'a, A, B>>),
549 fa: Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>),
550 ) -> Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, B>) {
551 ff.bind(move |f| {
552 fa.map(
553 #[allow(clippy::redundant_closure)] // Required for move semantics
554 move |a| f(a),
555 )
556 })
557 }
558 }
559
560 impl Semimonad for ThunkBrand {
561 /// Chains `Thunk` computations.
562 ///
563 /// ### Type Signature
564 ///
565 #[document_signature]
566 ///
567 /// ### Type Parameters
568 ///
569 #[document_type_parameters(
570 "The lifetime of the computation.",
571 "The type of the result of the first computation.",
572 "The type of the result of the new computation.",
573 "The type of the function to apply."
574 )]
575 ///
576 /// ### Parameters
577 ///
578 #[document_parameters(
579 "The first `Thunk`.",
580 "The function to apply to the result of the computation."
581 )]
582 ///
583 /// ### Returns
584 ///
585 /// A new `Thunk` instance representing the chained computation.
586 ///
587 /// ### Examples
588 ///
589 /// ```
590 /// use fp_library::{brands::*, functions::*};
591 ///
592 /// let thunk = pure::<ThunkBrand, _>(10);
593 /// let result = bind::<ThunkBrand, _, _, _>(thunk, |x| pure::<ThunkBrand, _>(x * 2));
594 /// assert_eq!(result.evaluate(), 20);
595 /// ```
596 fn bind<'a, A: 'a, B: 'a, Func>(
597 ma: Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>),
598 func: Func,
599 ) -> Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, B>)
600 where
601 Func: Fn(A) -> Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, B>) + 'a,
602 {
603 ma.bind(func)
604 }
605 }
606
607 impl MonadRec for ThunkBrand {
608 /// Performs tail-recursive monadic computation.
609 ///
610 /// ### Type Signature
611 ///
612 #[document_signature]
613 ///
614 /// ### Type Parameters
615 ///
616 #[document_type_parameters(
617 "The lifetime of the computation.",
618 "The type of the initial value and loop state.",
619 "The type of the result.",
620 "The type of the step function."
621 )]
622 ///
623 /// ### Parameters
624 ///
625 #[document_parameters("The step function.", "The initial value.")]
626 ///
627 /// ### Returns
628 ///
629 /// The result of the computation.
630 ///
631 /// ### Examples
632 ///
633 /// ```
634 /// use fp_library::{brands::*, classes::*, functions::*, types::*};
635 ///
636 /// let result = tail_rec_m::<ThunkBrand, _, _, _>(
637 /// |x| pure::<ThunkBrand, _>(if x < 1000 { Step::Loop(x + 1) } else { Step::Done(x) }),
638 /// 0,
639 /// );
640 /// assert_eq!(result.evaluate(), 1000);
641 /// ```
642 fn tail_rec_m<'a, A: 'a, B: 'a, F>(
643 f: F,
644 a: A,
645 ) -> Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, B>)
646 where
647 F: Fn(A) -> Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, Step<A, B>>)
648 + Clone
649 + 'a,
650 {
651 Thunk::new(move || {
652 let mut current = a;
653 loop {
654 match f(current).evaluate() {
655 Step::Loop(next) => current = next,
656 Step::Done(res) => break res,
657 }
658 }
659 })
660 }
661 }
662
663 impl Evaluable for ThunkBrand {
664 /// Runs the eval, producing the inner value.
665 ///
666 /// ### Type Signature
667 ///
668 #[document_signature]
669 ///
670 /// ### Type Parameters
671 ///
672 #[document_type_parameters(
673 "The lifetime of the computation.",
674 "The type of the value inside the thunk."
675 )]
676 ///
677 /// ### Parameters
678 ///
679 #[document_parameters("The eval to run.")]
680 ///
681 /// ### Returns
682 ///
683 /// The result of running the thunk.
684 ///
685 /// ### Examples
686 ///
687 /// ```
688 /// use fp_library::{brands::*, classes::*, functions::*, types::*};
689 ///
690 /// let thunk = Thunk::new(|| 42);
691 /// assert_eq!(evaluate::<ThunkBrand, _>(thunk), 42);
692 /// ```
693 fn evaluate<'a, A: 'a>(
694 fa: Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>)
695 ) -> A {
696 fa.evaluate()
697 }
698 }
699
700 impl Foldable for ThunkBrand {
701 /// Folds the `Thunk` from the right.
702 ///
703 /// ### Type Signature
704 ///
705 #[document_signature]
706 ///
707 /// ### Type Parameters
708 ///
709 #[document_type_parameters(
710 "The lifetime of the computation.",
711 "The brand of the cloneable function to use.",
712 "The type of the elements in the structure.",
713 "The type of the accumulator.",
714 "The type of the folding function."
715 )]
716 ///
717 /// ### Parameters
718 ///
719 #[document_parameters(
720 "The function to apply to each element and the accumulator.",
721 "The initial value of the accumulator.",
722 "The `Thunk` to fold."
723 )]
724 ///
725 /// ### Returns
726 ///
727 /// The final accumulator value.
728 ///
729 /// ### Examples
730 ///
731 /// ```
732 /// use fp_library::{brands::*, functions::*};
733 ///
734 /// let thunk = pure::<ThunkBrand, _>(10);
735 /// let result = fold_right::<RcFnBrand, ThunkBrand, _, _, _>(|a, b| a + b, 5, thunk);
736 /// assert_eq!(result, 15);
737 /// ```
738 fn fold_right<'a, FnBrand, A: 'a, B: 'a, Func>(
739 func: Func,
740 initial: B,
741 fa: Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>),
742 ) -> B
743 where
744 Func: Fn(A, B) -> B + 'a,
745 FnBrand: CloneableFn + 'a,
746 {
747 func(fa.evaluate(), initial)
748 }
749
750 /// Folds the `Thunk` from the left.
751 ///
752 /// ### Type Signature
753 ///
754 #[document_signature]
755 ///
756 /// ### Type Parameters
757 ///
758 #[document_type_parameters(
759 "The lifetime of the computation.",
760 "The brand of the cloneable function to use.",
761 "The type of the elements in the structure.",
762 "The type of the accumulator.",
763 "The type of the folding function."
764 )]
765 ///
766 /// ### Parameters
767 ///
768 #[document_parameters(
769 "The function to apply to the accumulator and each element.",
770 "The initial value of the accumulator.",
771 "The `Thunk` to fold."
772 )]
773 ///
774 /// ### Returns
775 ///
776 /// The final accumulator value.
777 ///
778 /// ### Examples
779 ///
780 /// ```
781 /// use fp_library::{brands::*, functions::*};
782 ///
783 /// let thunk = pure::<ThunkBrand, _>(10);
784 /// let result = fold_left::<RcFnBrand, ThunkBrand, _, _, _>(|b, a| b + a, 5, thunk);
785 /// assert_eq!(result, 15);
786 /// ```
787 fn fold_left<'a, FnBrand, A: 'a, B: 'a, Func>(
788 func: Func,
789 initial: B,
790 fa: Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>),
791 ) -> B
792 where
793 Func: Fn(B, A) -> B + 'a,
794 FnBrand: CloneableFn + 'a,
795 {
796 func(initial, fa.evaluate())
797 }
798
799 /// Maps the value to a monoid and returns it.
800 ///
801 /// ### Type Signature
802 ///
803 #[document_signature]
804 ///
805 /// ### Type Parameters
806 ///
807 #[document_type_parameters(
808 "The lifetime of the computation.",
809 "The brand of the cloneable function to use.",
810 "The type of the elements in the structure.",
811 "The type of the monoid.",
812 "The type of the mapping function."
813 )]
814 ///
815 /// ### Parameters
816 ///
817 #[document_parameters("The mapping function.", "The Thunk to fold.")]
818 ///
819 /// ### Returns
820 ///
821 /// The monoid value.
822 ///
823 /// ### Examples
824 ///
825 /// ```
826 /// use fp_library::{brands::*, functions::*};
827 ///
828 /// let thunk = pure::<ThunkBrand, _>(10);
829 /// let result = fold_map::<RcFnBrand, ThunkBrand, _, _, _>(|a| a.to_string(), thunk);
830 /// assert_eq!(result, "10");
831 /// ```
832 fn fold_map<'a, FnBrand, A: 'a, M, Func>(
833 func: Func,
834 fa: Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>),
835 ) -> M
836 where
837 M: Monoid + 'a,
838 Func: Fn(A) -> M + 'a,
839 FnBrand: CloneableFn + 'a,
840 {
841 func(fa.evaluate())
842 }
843 }
844
845 /// ### Type Parameters
846 ///
847 #[document_type_parameters(
848 "The lifetime of the computation.",
849 "The type of the value produced by the computation."
850 )]
851 impl<'a, A: Semigroup + 'a> Semigroup for Thunk<'a, A> {
852 /// Combines two `Thunk`s by combining their results.
853 ///
854 /// ### Type Signature
855 ///
856 #[document_signature]
857 ///
858 /// ### Parameters
859 ///
860 #[document_parameters("The first `Thunk`.", "The second `Thunk`.")]
861 ///
862 /// ### Returns
863 ///
864 /// A new `Thunk` containing the combined result.
865 ///
866 /// ### Examples
867 ///
868 /// ```
869 /// use fp_library::{brands::*, classes::*, functions::*};
870 ///
871 /// let t1 = pure::<ThunkBrand, _>("Hello".to_string());
872 /// let t2 = pure::<ThunkBrand, _>(" World".to_string());
873 /// let t3 = append::<_>(t1, t2);
874 /// assert_eq!(t3.evaluate(), "Hello World");
875 /// ```
876 fn append(
877 a: Self,
878 b: Self,
879 ) -> Self {
880 Thunk::new(move || Semigroup::append(a.evaluate(), b.evaluate()))
881 }
882 }
883
884 /// ### Type Parameters
885 ///
886 #[document_type_parameters(
887 "The lifetime of the computation.",
888 "The type of the value produced by the computation."
889 )]
890 impl<'a, A: Monoid + 'a> Monoid for Thunk<'a, A> {
891 /// Returns the identity `Thunk`.
892 ///
893 /// ### Type Signature
894 ///
895 #[document_signature]
896 ///
897 /// ### Returns
898 ///
899 /// A `Thunk` producing the identity value of `A`.
900 ///
901 /// ### Examples
902 ///
903 /// ```
904 /// use fp_library::{classes::*, types::*};
905 ///
906 /// let t: Thunk<String> = Thunk::empty();
907 /// assert_eq!(t.evaluate(), "");
908 /// ```
909 fn empty() -> Self {
910 Thunk::new(|| Monoid::empty())
911 }
912 }
913}
914pub use inner::*;
915
916#[cfg(test)]
917mod tests {
918 use super::*;
919
920 /// Tests basic execution of Thunk.
921 ///
922 /// Verifies that `Thunk::new` creates a computation that can be run to produce the expected value.
923 #[test]
924 fn test_basic_execution() {
925 let thunk = Thunk::new(|| 42);
926 assert_eq!(thunk.evaluate(), 42);
927 }
928
929 /// Tests `Thunk::pure`.
930 ///
931 /// Verifies that `Thunk::pure` creates a computation that returns the provided value.
932 #[test]
933 fn test_pure() {
934 let thunk = Thunk::pure(42);
935 assert_eq!(thunk.evaluate(), 42);
936 }
937
938 /// Tests borrowing in Thunk.
939 ///
940 /// Verifies that `Thunk` can capture references to values on the stack.
941 #[test]
942 fn test_borrowing() {
943 let x = 42;
944 let thunk = Thunk::new(|| &x);
945 assert_eq!(thunk.evaluate(), &42);
946 }
947
948 /// Tests `Thunk::map`.
949 ///
950 /// Verifies that `map` transforms the result of the computation.
951 #[test]
952 fn test_map() {
953 let thunk = Thunk::pure(21).map(|x| x * 2);
954 assert_eq!(thunk.evaluate(), 42);
955 }
956
957 /// Tests `Thunk::bind`.
958 ///
959 /// Verifies that `bind` chains computations correctly.
960 #[test]
961 fn test_bind() {
962 let thunk = Thunk::pure(21).bind(|x| Thunk::pure(x * 2));
963 assert_eq!(thunk.evaluate(), 42);
964 }
965
966 /// Tests `Thunk::defer`.
967 ///
968 /// Verifies that `defer` allows creating an `Thunk` from a thunk that returns an `Thunk`.
969 #[test]
970 fn test_defer() {
971 let thunk = Thunk::defer(|| Thunk::pure(42));
972 assert_eq!(thunk.evaluate(), 42);
973 }
974
975 /// Tests `From<Lazy>`.
976 #[test]
977 fn test_eval_from_memo() {
978 use crate::types::RcLazy;
979 let memo = RcLazy::new(|| 42);
980 let thunk = Thunk::from(memo);
981 assert_eq!(thunk.evaluate(), 42);
982 }
983
984 /// Tests the `Semigroup` implementation for `Thunk`.
985 ///
986 /// Verifies that `append` correctly combines two evals.
987 #[test]
988 fn test_eval_semigroup() {
989 use crate::classes::semigroup::append;
990 use crate::{brands::*, functions::*};
991 let t1 = pure::<ThunkBrand, _>("Hello".to_string());
992 let t2 = pure::<ThunkBrand, _>(" World".to_string());
993 let t3 = append(t1, t2);
994 assert_eq!(t3.evaluate(), "Hello World");
995 }
996
997 /// Tests the `Monoid` implementation for `Thunk`.
998 ///
999 /// Verifies that `empty` returns the identity element.
1000 #[test]
1001 fn test_eval_monoid() {
1002 use crate::classes::monoid::empty;
1003 let t: Thunk<String> = empty();
1004 assert_eq!(t.evaluate(), "");
1005 }
1006}