Skip to main content

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