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
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}