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