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