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