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. Does not cache results; if you need the same computation's result more than once, wrap it in [`Lazy`](crate::types::Lazy). 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				Extend,
17				Extract,
18				Foldable,
19				FoldableWithIndex,
20				Functor,
21				FunctorWithIndex,
22				LazyConfig,
23				Lift,
24				MonadRec,
25				Monoid,
26				Pointed,
27				Semiapplicative,
28				Semigroup,
29				Semimonad,
30				WithIndex,
31			},
32			impl_kind,
33			kinds::*,
34			types::{
35				ArcLazyConfig,
36				Lazy,
37				RcLazyConfig,
38				SendThunk,
39				Trampoline,
40			},
41		},
42		core::ops::ControlFlow,
43		fp_macros::*,
44		std::fmt,
45	};
46
47	/// A deferred computation that produces a value of type `A`.
48	///
49	/// `Thunk` is NOT memoized and does not cache results. Since [`evaluate`](Thunk::evaluate) takes
50	/// `self` by value, a `Thunk` can only be evaluated once. If you need the result more than once,
51	/// wrap it in [`Lazy`](crate::types::Lazy) via [`into_rc_lazy`](Thunk::into_rc_lazy).
52	///
53	/// Unlike [`Trampoline`](crate::types::Trampoline), `Thunk` does NOT require `'static` and CAN implement
54	/// HKT traits like [`Functor`], [`Semimonad`], etc.
55	///
56	/// ### Higher-Kinded Type Representation
57	///
58	/// The higher-kinded representation of this type constructor is [`ThunkBrand`](crate::brands::ThunkBrand),
59	/// which is fully polymorphic over the result type.
60	///
61	/// ### Trade-offs vs `Trampoline`
62	///
63	/// | Aspect         | `Thunk<'a, A>`              | `Trampoline<A>`              |
64	/// |----------------|-----------------------------|------------------------------ |
65	/// | HKT compatible | Yes                         | No (requires `'static`)      |
66	/// | Stack-safe     | Partial (tail_rec_m only)    | Yes (unlimited)              |
67	/// | Lifetime       | `'a` (can borrow)           | `'static` only               |
68	/// | Thread safety  | Not `Send`                  | Not `Send` (`A: 'static`)    |
69	/// | Use case       | Glue code, composition      | Deep recursion, pipelines    |
70	///
71	/// ### Algebraic Properties
72	///
73	/// `Thunk` is a proper Monad:
74	/// - `pure(a).evaluate() == a` (left identity).
75	/// - `thunk.bind(pure) == thunk` (right identity).
76	/// - `thunk.bind(f).bind(g) == thunk.bind(|a| f(a).bind(g))` (associativity).
77	///
78	/// ### Stack Safety
79	///
80	/// `Thunk::bind` chains are **not** stack-safe. Each nested [`bind`](Thunk::bind) adds a
81	/// frame to the call stack, so sufficiently deep chains will cause a stack overflow.
82	///
83	/// For stack-safe recursion within `Thunk`, use [`tail_rec_m`](crate::functions::tail_rec_m), which
84	/// uses an internal loop to avoid growing the stack.
85	///
86	/// For unlimited stack safety on all operations (including `bind` chains of arbitrary
87	/// depth), convert to [`Trampoline`](crate::types::Trampoline) instead, which is built
88	/// on the [`Free`](crate::types::Free) monad and guarantees O(1) stack usage.
89	///
90	/// ### Limitations
91	///
92	/// **Cannot implement `Traversable`**: The [`Traversable`](crate::classes::Traversable) trait
93	/// requires `Self::Of<'a, B>: Clone` (i.e., `Thunk<'a, B>: Clone`) in both `traverse` and
94	/// `sequence`. `Thunk` wraps `Box<dyn FnOnce() -> A>`, which cannot implement `Clone`
95	/// because `FnOnce` closures are consumed on invocation and `Box<dyn FnOnce>` does not
96	/// support cloning. Since the trait bounds on `Traversable` are fixed, there is no way
97	/// to implement the trait for `Thunk` without changing its internal representation.
98	/// This is an intentional trade-off: `Thunk` prioritizes zero-overhead deferred execution
99	/// and lifetime flexibility over structural cloning.
100	///
101	/// Implemented typeclasses:
102	/// - [`Functor`], [`Foldable`], [`Semimonad`]/Monad, [`Semiapplicative`]/Applicative
103	/// - Not [`Traversable`](crate::classes::Traversable) (requires `Clone`)
104	#[document_type_parameters(
105		"The lifetime of the computation.",
106		"The type of the value produced by the computation."
107	)]
108	///
109	pub struct Thunk<'a, A>(
110		/// The closure that performs the computation.
111		Box<dyn FnOnce() -> A + 'a>,
112	);
113
114	#[document_type_parameters(
115		"The lifetime of the computation.",
116		"The type of the value produced by the computation."
117	)]
118	#[document_parameters("The thunk instance.")]
119	impl<'a, A: 'a> Thunk<'a, A> {
120		/// Creates a new `Thunk` from a thunk.
121		#[document_signature]
122		///
123		#[document_parameters("The thunk to wrap.")]
124		///
125		#[document_returns("A new `Thunk` instance.")]
126		///
127		#[document_examples]
128		///
129		/// ```
130		/// use fp_library::types::*;
131		///
132		/// let thunk = Thunk::new(|| 42);
133		/// assert_eq!(thunk.evaluate(), 42);
134		/// ```
135		#[inline]
136		pub fn new(f: impl FnOnce() -> A + 'a) -> Self {
137			Thunk(Box::new(f))
138		}
139
140		/// Returns a pure value (already computed).
141		#[document_signature]
142		///
143		#[document_parameters("The value to wrap.")]
144		///
145		#[document_returns("A new `Thunk` instance containing the value.")]
146		///
147		#[document_examples]
148		///
149		/// ```
150		/// use fp_library::{
151		/// 	brands::*,
152		/// 	classes::*,
153		/// 	functions::*,
154		/// };
155		///
156		/// let thunk = pure::<ThunkBrand, _>(42);
157		/// assert_eq!(thunk.evaluate(), 42);
158		/// ```
159		#[inline]
160		pub fn pure(a: A) -> Self
161		where
162			A: 'a, {
163			Thunk::new(move || a)
164		}
165
166		/// Defers a computation that returns a Thunk.
167		#[document_signature]
168		///
169		#[document_parameters("The thunk that returns a `Thunk`.")]
170		///
171		#[document_returns("A new `Thunk` instance.")]
172		///
173		#[document_examples]
174		///
175		/// ```
176		/// use fp_library::{
177		/// 	brands::*,
178		/// 	functions::*,
179		/// 	types::*,
180		/// };
181		///
182		/// let thunk = Thunk::defer(|| pure::<ThunkBrand, _>(42));
183		/// assert_eq!(thunk.evaluate(), 42);
184		/// ```
185		#[inline]
186		pub fn defer(f: impl FnOnce() -> Thunk<'a, A> + 'a) -> Self {
187			Thunk::new(move || f().evaluate())
188		}
189
190		/// Monadic bind: chains computations.
191		///
192		/// Note: Each `bind` adds to the call stack. For deep recursion,
193		/// use [`Trampoline`](crate::types::Trampoline) instead.
194		///
195		/// This inherent method accepts [`FnOnce`] for maximum flexibility. The HKT-level
196		/// [`Semimonad::bind`](crate::classes::Semimonad::bind) requires [`Fn`] instead,
197		/// because some types (such as `Vec`) need to call the function multiple times.
198		/// Prefer this inherent method when you do not need HKT generality.
199		#[document_signature]
200		///
201		#[document_type_parameters("The type of the result of the new computation.")]
202		///
203		#[document_parameters("The function to apply to the result of the computation.")]
204		///
205		#[document_returns("A new `Thunk` instance representing the chained computation.")]
206		///
207		#[document_examples]
208		///
209		/// ```
210		/// use fp_library::{
211		/// 	brands::*,
212		/// 	functions::*,
213		/// };
214		///
215		/// let thunk = pure::<ThunkBrand, _>(21).bind(|x| pure::<ThunkBrand, _>(x * 2));
216		/// assert_eq!(thunk.evaluate(), 42);
217		/// ```
218		#[inline]
219		pub fn bind<B: 'a>(
220			self,
221			f: impl FnOnce(A) -> Thunk<'a, B> + 'a,
222		) -> Thunk<'a, B> {
223			Thunk::new(move || {
224				let a = (self.0)();
225				let thunk_b = f(a);
226				(thunk_b.0)()
227			})
228		}
229
230		/// Functor map: transforms the result.
231		///
232		/// This inherent method accepts `FnOnce`, which is more permissive than the
233		/// HKT [`Functor::map`] free function. The HKT version requires `Fn` because
234		/// the trait signature must support containers with multiple elements (e.g., `Vec`).
235		/// Since `Thunk` contains exactly one value, `FnOnce` suffices here. Prefer
236		/// this method when you do not need HKT polymorphism and want to pass a
237		/// non-reusable closure.
238		#[document_signature]
239		///
240		#[document_type_parameters("The type of the result of the transformation.")]
241		///
242		#[document_parameters("The function to apply to the result of the computation.")]
243		///
244		#[document_returns("A new `Thunk` instance with the transformed result.")]
245		///
246		#[document_examples]
247		///
248		/// ```
249		/// use fp_library::{
250		/// 	brands::*,
251		/// 	functions::*,
252		/// };
253		///
254		/// let thunk = pure::<ThunkBrand, _>(21).map(|x| x * 2);
255		/// assert_eq!(thunk.evaluate(), 42);
256		/// ```
257		#[inline]
258		pub fn map<B: 'a>(
259			self,
260			f: impl FnOnce(A) -> B + 'a,
261		) -> Thunk<'a, B> {
262			Thunk::new(move || f((self.0)()))
263		}
264
265		/// Forces evaluation and returns the result.
266		#[document_signature]
267		///
268		#[document_returns("The result of the computation.")]
269		///
270		#[document_examples]
271		///
272		/// ```
273		/// use fp_library::{
274		/// 	brands::*,
275		/// 	functions::*,
276		/// };
277		///
278		/// let thunk = pure::<ThunkBrand, _>(42);
279		/// assert_eq!(thunk.evaluate(), 42);
280		/// ```
281		#[inline]
282		pub fn evaluate(self) -> A {
283			(self.0)()
284		}
285
286		/// Converts this `Thunk` into a memoized [`Lazy`](crate::types::Lazy) value.
287		///
288		/// The computation will be evaluated at most once; subsequent accesses
289		/// return the cached result.
290		#[document_signature]
291		///
292		#[document_returns("A memoized `Lazy` value that evaluates this thunk on first access.")]
293		///
294		#[document_examples]
295		///
296		/// ```
297		/// use fp_library::types::*;
298		///
299		/// let thunk = Thunk::new(|| 42);
300		/// let lazy = thunk.into_rc_lazy();
301		/// assert_eq!(*lazy.evaluate(), 42);
302		/// ```
303		#[inline]
304		pub fn into_rc_lazy(self) -> Lazy<'a, A, RcLazyConfig> {
305			Lazy::from(self)
306		}
307
308		/// Evaluates this `Thunk` and wraps the result in a thread-safe [`ArcLazy`](crate::types::Lazy).
309		///
310		/// The thunk is evaluated eagerly because its inner closure is `!Send`
311		/// (it is stored as `Box<dyn FnOnce() -> A + 'a>`), so it cannot be
312		/// placed inside an `Arc`-based lazy value that requires `Send`. By
313		/// evaluating first, only the resulting `A` (which is `Send + Sync`)
314		/// needs to cross the thread-safety boundary.
315		#[document_signature]
316		///
317		#[document_returns("A thread-safe `ArcLazy` containing the eagerly evaluated result.")]
318		///
319		#[document_examples]
320		///
321		/// ```
322		/// use fp_library::types::*;
323		///
324		/// let thunk = Thunk::new(|| 42);
325		/// let lazy = thunk.into_arc_lazy();
326		/// assert_eq!(*lazy.evaluate(), 42);
327		/// ```
328		#[inline]
329		pub fn into_arc_lazy(self) -> Lazy<'a, A, ArcLazyConfig>
330		where
331			A: Send + Sync + 'a, {
332			let val = self.evaluate();
333			Lazy::<'a, A, ArcLazyConfig>::new(move || val)
334		}
335	}
336
337	#[document_type_parameters(
338		"The lifetime of the computation.",
339		"The type of the value produced by the computation.",
340		"The memoization configuration."
341	)]
342	impl<'a, A, Config> From<Lazy<'a, A, Config>> for Thunk<'a, A>
343	where
344		A: Clone + 'a,
345		Config: LazyConfig,
346	{
347		/// Converts a [`Lazy`] value into a [`Thunk`] by cloning the memoized value.
348		///
349		/// This conversion clones the cached value on each evaluation.
350		/// The cost depends on the [`Clone`] implementation of `A`.
351		#[document_signature]
352		#[document_parameters("The lazy value to convert.")]
353		#[document_returns("A thunk that evaluates the lazy value.")]
354		#[document_examples]
355		///
356		/// ```
357		/// use fp_library::types::*;
358		/// let lazy = Lazy::<_, RcLazyConfig>::pure(42);
359		/// let thunk = Thunk::from(lazy);
360		/// assert_eq!(thunk.evaluate(), 42);
361		/// ```
362		fn from(lazy: Lazy<'a, A, Config>) -> Self {
363			Thunk::new(move || lazy.evaluate().clone())
364		}
365	}
366
367	#[document_type_parameters(
368		"The lifetime of the computation.",
369		"The type of the value produced by the computation."
370	)]
371	impl<'a, A: 'a> From<SendThunk<'a, A>> for Thunk<'a, A> {
372		/// Converts a [`SendThunk`] into a [`Thunk`] by erasing the `Send` bound.
373		///
374		/// This is a zero-cost unsizing coercion: the inner
375		/// `Box<dyn FnOnce() -> A + Send + 'a>` is coerced to
376		/// `Box<dyn FnOnce() -> A + 'a>`, which the compiler performs
377		/// without any runtime overhead.
378		#[document_signature]
379		#[document_parameters("The send thunk to convert.")]
380		#[document_returns("A `Thunk` wrapping the same deferred computation.")]
381		#[document_examples]
382		///
383		/// ```
384		/// use fp_library::types::*;
385		/// let send_thunk = SendThunk::pure(42);
386		/// let thunk = Thunk::from(send_thunk);
387		/// assert_eq!(thunk.evaluate(), 42);
388		/// ```
389		fn from(send_thunk: SendThunk<'a, A>) -> Self {
390			Thunk(send_thunk.into_inner())
391		}
392	}
393
394	#[document_type_parameters("The type of the value produced by the computation.")]
395	impl<A: 'static> From<crate::types::Trampoline<A>> for Thunk<'static, A> {
396		#[document_signature]
397		#[document_parameters("The trampoline to convert.")]
398		#[document_returns("A thunk that evaluates the trampoline.")]
399		#[document_examples]
400		///
401		/// ```
402		/// use fp_library::types::*;
403		/// let task = Trampoline::pure(42);
404		/// let thunk = Thunk::from(task);
405		/// assert_eq!(thunk.evaluate(), 42);
406		/// ```
407		fn from(trampoline: crate::types::Trampoline<A>) -> Self {
408			Thunk::new(move || trampoline.evaluate())
409		}
410	}
411
412	#[document_type_parameters("The type of the value produced by the computation.")]
413	impl<A: 'static> From<Thunk<'static, A>> for Trampoline<A> {
414		/// Converts a `'static` `Thunk` into a `Trampoline`.
415		///
416		/// This lifts a non-stack-safe `Thunk` into the stack-safe `Trampoline`
417		/// execution model. The resulting `Trampoline` evaluates the thunk when run.
418		#[document_signature]
419		#[document_parameters("The thunk to convert.")]
420		#[document_returns("A trampoline that evaluates the thunk.")]
421		#[document_examples]
422		///
423		/// ```
424		/// use fp_library::types::*;
425		/// let thunk = Thunk::new(|| 42);
426		/// let trampoline = Trampoline::from(thunk);
427		/// assert_eq!(trampoline.evaluate(), 42);
428		/// ```
429		fn from(thunk: Thunk<'static, A>) -> Self {
430			Trampoline::new(move || thunk.evaluate())
431		}
432	}
433
434	impl_kind! {
435		for ThunkBrand {
436			type Of<'a, A: 'a>: 'a = Thunk<'a, A>;
437		}
438	}
439
440	#[document_type_parameters(
441		"The lifetime of the computation.",
442		"The type of the value produced by the computation."
443	)]
444	impl<'a, A: 'a> Deferrable<'a> for Thunk<'a, A> {
445		/// Creates a `Thunk` from a computation that produces it.
446		#[document_signature]
447		///
448		#[document_parameters("A thunk that produces the thunk.")]
449		///
450		#[document_returns("The deferred thunk.")]
451		///
452		#[document_examples]
453		///
454		/// ```
455		/// use fp_library::{
456		/// 	brands::*,
457		/// 	classes::Deferrable,
458		/// 	functions::*,
459		/// 	types::*,
460		/// };
461		///
462		/// let task: Thunk<i32> = Deferrable::defer(|| Thunk::pure(42));
463		/// assert_eq!(task.evaluate(), 42);
464		/// ```
465		fn defer(f: impl FnOnce() -> Self + 'a) -> Self
466		where
467			Self: Sized, {
468			Thunk::defer(f)
469		}
470	}
471
472	impl Functor for ThunkBrand {
473		/// Maps a function over the result of a `Thunk` computation.
474		#[document_signature]
475		///
476		#[document_type_parameters(
477			"The lifetime of the computation.",
478			"The type of the value inside the `Thunk`.",
479			"The type of the result of the transformation."
480		)]
481		///
482		#[document_parameters(
483			"The function to apply to the result of the computation.",
484			"The `Thunk` instance."
485		)]
486		///
487		#[document_returns("A new `Thunk` instance with the transformed result.")]
488		#[document_examples]
489		///
490		/// ```
491		/// use fp_library::{
492		/// 	brands::*,
493		/// 	functions::*,
494		/// };
495		///
496		/// let thunk = pure::<ThunkBrand, _>(10);
497		/// let mapped = map::<ThunkBrand, _, _>(|x| x * 2, thunk);
498		/// assert_eq!(mapped.evaluate(), 20);
499		/// ```
500		fn map<'a, A: 'a, B: 'a>(
501			func: impl Fn(A) -> B + 'a,
502			fa: Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>),
503		) -> Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, B>) {
504			fa.map(func)
505		}
506	}
507
508	impl Pointed for ThunkBrand {
509		/// Wraps a value in a `Thunk` context.
510		#[document_signature]
511		///
512		#[document_type_parameters(
513			"The lifetime of the computation.",
514			"The type of the value to wrap."
515		)]
516		///
517		#[document_parameters("The value to wrap.")]
518		///
519		#[document_returns("A new `Thunk` instance containing the value.")]
520		///
521		#[document_examples]
522		///
523		/// ```
524		/// use fp_library::{
525		/// 	brands::*,
526		/// 	functions::*,
527		/// 	types::*,
528		/// };
529		///
530		/// let thunk: Thunk<i32> = pure::<ThunkBrand, _>(42);
531		/// assert_eq!(thunk.evaluate(), 42);
532		/// ```
533		fn pure<'a, A: 'a>(a: A) -> Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>) {
534			Thunk::pure(a)
535		}
536	}
537
538	impl Lift for ThunkBrand {
539		/// Lifts a binary function into the `Thunk` context.
540		#[document_signature]
541		///
542		#[document_type_parameters(
543			"The lifetime of the computation.",
544			"The type of the first value.",
545			"The type of the second value.",
546			"The type of the result."
547		)]
548		///
549		#[document_parameters(
550			"The binary function to apply.",
551			"The first `Thunk`.",
552			"The second `Thunk`."
553		)]
554		///
555		#[document_returns(
556			"A new `Thunk` instance containing the result of applying the function."
557		)]
558		#[document_examples]
559		///
560		/// ```
561		/// use fp_library::{
562		/// 	brands::*,
563		/// 	functions::*,
564		/// };
565		///
566		/// let eval1 = pure::<ThunkBrand, _>(10);
567		/// let eval2 = pure::<ThunkBrand, _>(20);
568		/// let result = lift2::<ThunkBrand, _, _, _>(|a, b| a + b, eval1, eval2);
569		/// assert_eq!(result.evaluate(), 30);
570		/// ```
571		fn lift2<'a, A, B, C>(
572			func: impl Fn(A, B) -> C + 'a,
573			fa: Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>),
574			fb: Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, B>),
575		) -> Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, C>)
576		where
577			A: Clone + 'a,
578			B: Clone + 'a,
579			C: 'a, {
580			fa.bind(move |a| fb.map(move |b| func(a, b)))
581		}
582	}
583
584	impl ApplyFirst for ThunkBrand {}
585	impl ApplySecond for ThunkBrand {}
586
587	impl Semiapplicative for ThunkBrand {
588		/// Applies a function wrapped in `Thunk` to a value wrapped in `Thunk`.
589		#[document_signature]
590		///
591		#[document_type_parameters(
592			"The lifetime of the computation.",
593			"The brand of the cloneable function wrapper.",
594			"The type of the input.",
595			"The type of the result."
596		)]
597		///
598		#[document_parameters(
599			"The `Thunk` containing the function.",
600			"The `Thunk` containing the value."
601		)]
602		///
603		#[document_returns(
604			"A new `Thunk` instance containing the result of applying the function."
605		)]
606		#[document_examples]
607		///
608		/// ```
609		/// use fp_library::{
610		/// 	brands::*,
611		/// 	functions::*,
612		/// };
613		///
614		/// let func = pure::<ThunkBrand, _>(cloneable_fn_new::<RcFnBrand, _, _>(|x: i32| x * 2));
615		/// let val = pure::<ThunkBrand, _>(21);
616		/// let result = apply::<RcFnBrand, ThunkBrand, _, _>(func, val);
617		/// assert_eq!(result.evaluate(), 42);
618		/// ```
619		fn apply<'a, FnBrand: 'a + CloneableFn, A: 'a + Clone, B: 'a>(
620			ff: Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, <FnBrand as CloneableFn>::Of<'a, A, B>>),
621			fa: Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>),
622		) -> Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, B>) {
623			ff.bind(move |f| {
624				fa.map(
625					#[allow(clippy::redundant_closure)] // Required for move semantics
626					move |a| f(a),
627				)
628			})
629		}
630	}
631
632	impl Semimonad for ThunkBrand {
633		/// Chains `Thunk` computations.
634		#[document_signature]
635		///
636		#[document_type_parameters(
637			"The lifetime of the computation.",
638			"The type of the result of the first computation.",
639			"The type of the result of the new computation."
640		)]
641		///
642		#[document_parameters(
643			"The first `Thunk`.",
644			"The function to apply to the result of the computation."
645		)]
646		///
647		#[document_returns("A new `Thunk` instance representing the chained computation.")]
648		#[document_examples]
649		///
650		/// ```
651		/// use fp_library::{
652		/// 	brands::*,
653		/// 	functions::*,
654		/// };
655		///
656		/// let thunk = pure::<ThunkBrand, _>(10);
657		/// let result = bind::<ThunkBrand, _, _>(thunk, |x| pure::<ThunkBrand, _>(x * 2));
658		/// assert_eq!(result.evaluate(), 20);
659		/// ```
660		fn bind<'a, A: 'a, B: 'a>(
661			ma: Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>),
662			func: impl Fn(A) -> Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, B>) + 'a,
663		) -> Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, B>) {
664			ma.bind(func)
665		}
666	}
667
668	impl MonadRec for ThunkBrand {
669		/// Performs tail-recursive monadic computation.
670		///
671		/// The step function `f` should return shallow thunks (ideally [`Thunk::pure`]
672		/// or a single-level [`Thunk::new`]). If `f` builds deep [`bind`](Thunk::bind)
673		/// chains inside the returned thunk, the internal [`evaluate`](Thunk::evaluate)
674		/// call can still overflow the stack.
675		#[document_signature]
676		///
677		#[document_type_parameters(
678			"The lifetime of the computation.",
679			"The type of the initial value and loop state.",
680			"The type of the result."
681		)]
682		///
683		#[document_parameters("The step function.", "The initial value.")]
684		///
685		#[document_returns("The result of the computation.")]
686		///
687		#[document_examples]
688		///
689		/// ```
690		/// use {
691		/// 	core::ops::ControlFlow,
692		/// 	fp_library::{
693		/// 		brands::*,
694		/// 		classes::*,
695		/// 		functions::*,
696		/// 		types::*,
697		/// 	},
698		/// };
699		///
700		/// let result = tail_rec_m::<ThunkBrand, _, _>(
701		/// 	|x| {
702		/// 		pure::<ThunkBrand, _>(
703		/// 			if x < 1000 { ControlFlow::Continue(x + 1) } else { ControlFlow::Break(x) },
704		/// 		)
705		/// 	},
706		/// 	0,
707		/// );
708		/// assert_eq!(result.evaluate(), 1000);
709		/// ```
710		fn tail_rec_m<'a, A: 'a, B: 'a>(
711			f: impl Fn(
712				A,
713			)
714				-> Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, ControlFlow<B, A>>)
715			+ 'a,
716			a: A,
717		) -> Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, B>) {
718			Thunk::new(move || {
719				let mut current = a;
720				loop {
721					match f(current).evaluate() {
722						ControlFlow::Continue(next) => current = next,
723						ControlFlow::Break(res) => break res,
724					}
725				}
726			})
727		}
728	}
729
730	impl Extract for ThunkBrand {
731		/// Extracts the inner value from a thunk by running it.
732		#[document_signature]
733		///
734		#[document_type_parameters(
735			"The lifetime of the computation.",
736			"The type of the value inside the thunk."
737		)]
738		///
739		#[document_parameters("The thunk to extract from.")]
740		///
741		#[document_returns("The result of running the thunk.")]
742		///
743		#[document_examples]
744		///
745		/// ```
746		/// use fp_library::{
747		/// 	brands::*,
748		/// 	classes::*,
749		/// 	functions::*,
750		/// 	types::*,
751		/// };
752		///
753		/// let thunk = Thunk::new(|| 42);
754		/// assert_eq!(extract::<ThunkBrand, _>(thunk), 42);
755		/// ```
756		fn extract<'a, A: 'a>(
757			fa: Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>)
758		) -> A {
759			fa.evaluate()
760		}
761	}
762
763	impl Extend for ThunkBrand {
764		/// Extends a local computation to the `Thunk` context.
765		///
766		/// Wraps the application of `f` to the entire thunk in a new deferred
767		/// computation. The resulting `Thunk` captures both `f` and `thunk` by
768		/// move and calls `f(thunk)` when evaluated.
769		#[document_signature]
770		///
771		#[document_type_parameters(
772			"The lifetime of the computation.",
773			"The type of the value inside the thunk.",
774			"The result type of the extension function."
775		)]
776		///
777		#[document_parameters(
778			"The function that consumes a whole thunk and produces a value.",
779			"The thunk to extend over."
780		)]
781		///
782		#[document_returns("A new thunk containing the deferred result of applying the function.")]
783		///
784		#[document_examples]
785		///
786		/// ```
787		/// use fp_library::{
788		/// 	brands::*,
789		/// 	functions::*,
790		/// 	types::*,
791		/// };
792		///
793		/// let thunk = Thunk::new(|| 21);
794		/// let result = extend::<ThunkBrand, _, _>(|w: Thunk<i32>| w.evaluate() * 2, thunk);
795		/// assert_eq!(result.evaluate(), 42);
796		/// ```
797		fn extend<'a, A: 'a + Clone, B: 'a>(
798			f: impl Fn(Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>)) -> B + 'a,
799			wa: Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>),
800		) -> Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, B>) {
801			Thunk::new(move || f(wa))
802		}
803	}
804
805	impl Foldable for ThunkBrand {
806		/// Folds the `Thunk` from the right.
807		#[document_signature]
808		///
809		#[document_type_parameters(
810			"The lifetime of the computation.",
811			"The brand of the cloneable function to use.",
812			"The type of the elements in the structure.",
813			"The type of the accumulator."
814		)]
815		///
816		#[document_parameters(
817			"The function to apply to each element and the accumulator.",
818			"The initial value of the accumulator.",
819			"The `Thunk` to fold."
820		)]
821		///
822		#[document_returns("The final accumulator value.")]
823		#[document_examples]
824		///
825		/// ```
826		/// use fp_library::{
827		/// 	brands::*,
828		/// 	functions::*,
829		/// };
830		///
831		/// let thunk = pure::<ThunkBrand, _>(10);
832		/// let result = fold_right::<RcFnBrand, ThunkBrand, _, _>(|a, b| a + b, 5, thunk);
833		/// assert_eq!(result, 15);
834		/// ```
835		fn fold_right<'a, FnBrand, A: 'a + Clone, B: 'a>(
836			func: impl Fn(A, B) -> B + 'a,
837			initial: B,
838			fa: Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>),
839		) -> B
840		where
841			FnBrand: CloneableFn + 'a, {
842			func(fa.evaluate(), initial)
843		}
844
845		/// Folds the `Thunk` from the left.
846		#[document_signature]
847		///
848		#[document_type_parameters(
849			"The lifetime of the computation.",
850			"The brand of the cloneable function to use.",
851			"The type of the elements in the structure.",
852			"The type of the accumulator."
853		)]
854		///
855		#[document_parameters(
856			"The function to apply to the accumulator and each element.",
857			"The initial value of the accumulator.",
858			"The `Thunk` to fold."
859		)]
860		///
861		#[document_returns("The final accumulator value.")]
862		#[document_examples]
863		///
864		/// ```
865		/// use fp_library::{
866		/// 	brands::*,
867		/// 	functions::*,
868		/// };
869		///
870		/// let thunk = pure::<ThunkBrand, _>(10);
871		/// let result = fold_left::<RcFnBrand, ThunkBrand, _, _>(|b, a| b + a, 5, thunk);
872		/// assert_eq!(result, 15);
873		/// ```
874		fn fold_left<'a, FnBrand, A: 'a + Clone, B: 'a>(
875			func: impl Fn(B, A) -> B + 'a,
876			initial: B,
877			fa: Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>),
878		) -> B
879		where
880			FnBrand: CloneableFn + 'a, {
881			func(initial, fa.evaluate())
882		}
883
884		/// Maps the value to a monoid and returns it.
885		#[document_signature]
886		///
887		#[document_type_parameters(
888			"The lifetime of the computation.",
889			"The brand of the cloneable function to use.",
890			"The type of the elements in the structure.",
891			"The type of the monoid."
892		)]
893		///
894		#[document_parameters("The mapping function.", "The Thunk to fold.")]
895		///
896		#[document_returns("The monoid value.")]
897		///
898		#[document_examples]
899		///
900		/// ```
901		/// use fp_library::{
902		/// 	brands::*,
903		/// 	functions::*,
904		/// };
905		///
906		/// let thunk = pure::<ThunkBrand, _>(10);
907		/// let result = fold_map::<RcFnBrand, ThunkBrand, _, _>(|a| a.to_string(), thunk);
908		/// assert_eq!(result, "10");
909		/// ```
910		fn fold_map<'a, FnBrand, A: 'a + Clone, M>(
911			func: impl Fn(A) -> M + 'a,
912			fa: Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>),
913		) -> M
914		where
915			M: Monoid + 'a,
916			FnBrand: CloneableFn + 'a, {
917			func(fa.evaluate())
918		}
919	}
920
921	impl WithIndex for ThunkBrand {
922		type Index = ();
923	}
924
925	impl FunctorWithIndex for ThunkBrand {
926		/// Maps a function over the value in the thunk, providing the index `()`.
927		#[document_signature]
928		#[document_type_parameters(
929			"The lifetime of the computation.",
930			"The type of the value inside the thunk.",
931			"The type of the result of applying the function."
932		)]
933		#[document_parameters(
934			"The function to apply to the value and its index.",
935			"The thunk to map over."
936		)]
937		#[document_returns("A new thunk containing the result of applying the function.")]
938		#[document_examples]
939		///
940		/// ```
941		/// use fp_library::{
942		/// 	brands::ThunkBrand,
943		/// 	classes::functor_with_index::FunctorWithIndex,
944		/// };
945		///
946		/// let thunk = fp_library::types::Thunk::pure(5);
947		/// let result = <ThunkBrand as FunctorWithIndex>::map_with_index(|_, x| x * 2, thunk);
948		/// assert_eq!(result.evaluate(), 10);
949		/// ```
950		fn map_with_index<'a, A: 'a, B: 'a>(
951			f: impl Fn((), A) -> B + 'a,
952			fa: Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>),
953		) -> Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, B>) {
954			fa.map(move |a| f((), a))
955		}
956	}
957
958	impl FoldableWithIndex for ThunkBrand {
959		/// Folds the thunk using a monoid, providing the index `()`.
960		#[document_signature]
961		#[document_type_parameters(
962			"The lifetime of the computation.",
963			"The type of the value inside the thunk.",
964			"The monoid type."
965		)]
966		#[document_parameters(
967			"The function to apply to the value and its index.",
968			"The thunk to fold."
969		)]
970		#[document_returns("The monoid value.")]
971		#[document_examples]
972		///
973		/// ```
974		/// use fp_library::{
975		/// 	brands::ThunkBrand,
976		/// 	classes::foldable_with_index::FoldableWithIndex,
977		/// };
978		///
979		/// let thunk = fp_library::types::Thunk::pure(5);
980		/// let result =
981		/// 	<ThunkBrand as FoldableWithIndex>::fold_map_with_index(|_, x: i32| x.to_string(), thunk);
982		/// assert_eq!(result, "5");
983		/// ```
984		fn fold_map_with_index<'a, A: 'a + Clone, R: Monoid>(
985			f: impl Fn((), A) -> R + 'a,
986			fa: Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>),
987		) -> R {
988			f((), fa.evaluate())
989		}
990	}
991
992	#[document_type_parameters(
993		"The lifetime of the computation.",
994		"The type of the value produced by the computation."
995	)]
996	impl<'a, A: Semigroup + 'a> Semigroup for Thunk<'a, A> {
997		/// Combines two `Thunk`s by combining their results.
998		#[document_signature]
999		///
1000		#[document_parameters("The first `Thunk`.", "The second `Thunk`.")]
1001		///
1002		#[document_returns("A new `Thunk` containing the combined result.")]
1003		///
1004		#[document_examples]
1005		///
1006		/// ```
1007		/// use fp_library::{
1008		/// 	brands::*,
1009		/// 	classes::*,
1010		/// 	functions::*,
1011		/// };
1012		///
1013		/// let t1 = pure::<ThunkBrand, _>("Hello".to_string());
1014		/// let t2 = pure::<ThunkBrand, _>(" World".to_string());
1015		/// let t3 = append::<_>(t1, t2);
1016		/// assert_eq!(t3.evaluate(), "Hello World");
1017		/// ```
1018		fn append(
1019			a: Self,
1020			b: Self,
1021		) -> Self {
1022			Thunk::new(move || Semigroup::append(a.evaluate(), b.evaluate()))
1023		}
1024	}
1025
1026	#[document_type_parameters(
1027		"The lifetime of the computation.",
1028		"The type of the value produced by the computation."
1029	)]
1030	impl<'a, A: Monoid + 'a> Monoid for Thunk<'a, A> {
1031		/// Returns the identity `Thunk`.
1032		#[document_signature]
1033		///
1034		#[document_returns("A `Thunk` producing the identity value of `A`.")]
1035		///
1036		#[document_examples]
1037		///
1038		/// ```
1039		/// use fp_library::{
1040		/// 	classes::*,
1041		/// 	types::*,
1042		/// };
1043		///
1044		/// let t: Thunk<String> = Thunk::empty();
1045		/// assert_eq!(t.evaluate(), "");
1046		/// ```
1047		fn empty() -> Self {
1048			Thunk::new(|| Monoid::empty())
1049		}
1050	}
1051
1052	#[document_type_parameters(
1053		"The lifetime of the computation.",
1054		"The type of the computed value."
1055	)]
1056	#[document_parameters("The thunk to format.")]
1057	impl<'a, A> fmt::Debug for Thunk<'a, A> {
1058		/// Formats the thunk without evaluating it.
1059		#[document_signature]
1060		#[document_parameters("The formatter.")]
1061		#[document_returns("The formatting result.")]
1062		#[document_examples]
1063		///
1064		/// ```
1065		/// use fp_library::types::*;
1066		/// let thunk = Thunk::pure(42);
1067		/// assert_eq!(format!("{:?}", thunk), "Thunk(<unevaluated>)");
1068		/// ```
1069		fn fmt(
1070			&self,
1071			f: &mut fmt::Formatter<'_>,
1072		) -> fmt::Result {
1073			f.write_str("Thunk(<unevaluated>)")
1074		}
1075	}
1076}
1077pub use inner::*;
1078
1079#[cfg(test)]
1080mod tests {
1081	use {
1082		super::*,
1083		crate::{
1084			brands::*,
1085			classes::{
1086				monoid::empty,
1087				semigroup::append,
1088			},
1089			functions::*,
1090		},
1091		quickcheck_macros::quickcheck,
1092	};
1093
1094	/// Tests basic execution of Thunk.
1095	///
1096	/// Verifies that `Thunk::new` creates a computation that can be run to produce the expected value.
1097	#[test]
1098	fn test_basic_execution() {
1099		let thunk = Thunk::new(|| 42);
1100		assert_eq!(thunk.evaluate(), 42);
1101	}
1102
1103	/// Tests `Thunk::pure`.
1104	///
1105	/// Verifies that `Thunk::pure` creates a computation that returns the provided value.
1106	#[test]
1107	fn test_pure() {
1108		let thunk = Thunk::pure(42);
1109		assert_eq!(thunk.evaluate(), 42);
1110	}
1111
1112	/// Tests borrowing in Thunk.
1113	///
1114	/// Verifies that `Thunk` can capture references to values on the stack.
1115	#[test]
1116	fn test_borrowing() {
1117		let x = 42;
1118		let thunk = Thunk::new(|| &x);
1119		assert_eq!(thunk.evaluate(), &42);
1120	}
1121
1122	/// Tests `Thunk::map`.
1123	///
1124	/// Verifies that `map` transforms the result of the computation.
1125	#[test]
1126	fn test_map() {
1127		let thunk = Thunk::pure(21).map(|x| x * 2);
1128		assert_eq!(thunk.evaluate(), 42);
1129	}
1130
1131	/// Tests `Thunk::bind`.
1132	///
1133	/// Verifies that `bind` chains computations correctly.
1134	#[test]
1135	fn test_bind() {
1136		let thunk = Thunk::pure(21).bind(|x| Thunk::pure(x * 2));
1137		assert_eq!(thunk.evaluate(), 42);
1138	}
1139
1140	/// Tests `Thunk::defer`.
1141	///
1142	/// Verifies that `defer` allows creating a `Thunk` from a thunk that returns a `Thunk`.
1143	#[test]
1144	fn test_defer() {
1145		let thunk = Thunk::defer(|| Thunk::pure(42));
1146		assert_eq!(thunk.evaluate(), 42);
1147	}
1148
1149	/// Tests `From<Lazy>`.
1150	#[test]
1151	fn test_thunk_from_memo() {
1152		use crate::types::RcLazy;
1153		let memo = RcLazy::new(|| 42);
1154		let thunk = Thunk::from(memo);
1155		assert_eq!(thunk.evaluate(), 42);
1156	}
1157
1158	/// Tests `From<SendThunk>`.
1159	///
1160	/// Verifies that a `SendThunk` can be converted into a `Thunk` by erasing
1161	/// the `Send` bound, and that the resulting `Thunk` produces the same value.
1162	#[test]
1163	fn test_thunk_from_send_thunk() {
1164		use crate::types::SendThunk;
1165		let send_thunk = SendThunk::new(|| 21 * 2);
1166		let thunk = Thunk::from(send_thunk);
1167		assert_eq!(thunk.evaluate(), 42);
1168	}
1169
1170	/// Tests the `Semigroup` implementation for `Thunk`.
1171	///
1172	/// Verifies that `append` correctly combines two thunks.
1173	#[test]
1174	fn test_thunk_semigroup() {
1175		use crate::{
1176			brands::*,
1177			classes::semigroup::append,
1178			functions::*,
1179		};
1180		let t1 = pure::<ThunkBrand, _>("Hello".to_string());
1181		let t2 = pure::<ThunkBrand, _>(" World".to_string());
1182		let t3 = append(t1, t2);
1183		assert_eq!(t3.evaluate(), "Hello World");
1184	}
1185
1186	/// Tests the `Monoid` implementation for `Thunk`.
1187	///
1188	/// Verifies that `empty` returns the identity element.
1189	#[test]
1190	fn test_thunk_monoid() {
1191		use crate::classes::monoid::empty;
1192		let t: Thunk<String> = empty();
1193		assert_eq!(t.evaluate(), "");
1194	}
1195
1196	/// Tests `From<Trampoline>` for `Thunk`.
1197	///
1198	/// Verifies that converting a `Trampoline` to a `Thunk` preserves the computed value.
1199	#[test]
1200	fn test_thunk_from_trampoline() {
1201		use crate::types::Trampoline;
1202
1203		let task = Trampoline::pure(42);
1204		let thunk = Thunk::from(task);
1205		assert_eq!(thunk.evaluate(), 42);
1206	}
1207
1208	/// Tests roundtrip `Trampoline` -> `Thunk` -> evaluate with a lazy computation.
1209	///
1210	/// Verifies that a lazy trampoline is correctly evaluated when converted to a thunk.
1211	#[test]
1212	fn test_thunk_from_trampoline_lazy() {
1213		use crate::types::Trampoline;
1214
1215		let task = Trampoline::new(|| 21 * 2);
1216		let thunk = Thunk::from(task);
1217		assert_eq!(thunk.evaluate(), 42);
1218	}
1219
1220	/// Tests `From<Thunk<'static, A>> for Trampoline<A>`.
1221	///
1222	/// Verifies that a `'static` `Thunk` can be converted to a `Trampoline`.
1223	#[test]
1224	fn test_thunk_to_trampoline() {
1225		use crate::types::Trampoline;
1226		let thunk = Thunk::new(|| 42);
1227		let trampoline = Trampoline::from(thunk);
1228		assert_eq!(trampoline.evaluate(), 42);
1229	}
1230
1231	/// Tests `From<Thunk<'static, A>> for Trampoline<A>` with chained computation.
1232	///
1233	/// Verifies that conversion preserves the deferred computation.
1234	#[test]
1235	fn test_thunk_to_trampoline_chained() {
1236		use crate::types::Trampoline;
1237		let thunk = Thunk::pure(10).map(|x| x * 3).bind(|x| Thunk::pure(x + 12));
1238		let trampoline = Trampoline::from(thunk);
1239		assert_eq!(trampoline.evaluate(), 42);
1240	}
1241
1242	/// Tests `From<Thunk<'static, Rc<i32>>> for Trampoline<Rc<i32>>`.
1243	///
1244	/// Verifies that a non-`Send` type (`Rc`) can be converted now that the
1245	/// spurious `Send` bound has been removed.
1246	#[test]
1247	fn test_thunk_to_trampoline_non_send() {
1248		use {
1249			crate::types::Trampoline,
1250			std::rc::Rc,
1251		};
1252		let thunk = Thunk::new(|| Rc::new(42));
1253		let trampoline = Trampoline::from(thunk);
1254		assert_eq!(*trampoline.evaluate(), 42);
1255	}
1256
1257	// QuickCheck Law Tests
1258
1259	// Functor Laws
1260
1261	/// Functor identity: `map(identity, fa) == fa`.
1262	#[quickcheck]
1263	fn functor_identity(x: i32) -> bool {
1264		map::<ThunkBrand, _, _>(identity, pure::<ThunkBrand, _>(x)).evaluate() == x
1265	}
1266
1267	/// Functor composition: `map(f . g, fa) == map(f, map(g, fa))`.
1268	#[quickcheck]
1269	fn functor_composition(x: i32) -> bool {
1270		let f = |a: i32| a.wrapping_add(1);
1271		let g = |a: i32| a.wrapping_mul(2);
1272		let lhs = map::<ThunkBrand, _, _>(move |a| f(g(a)), pure::<ThunkBrand, _>(x)).evaluate();
1273		let rhs = map::<ThunkBrand, _, _>(f, map::<ThunkBrand, _, _>(g, pure::<ThunkBrand, _>(x)))
1274			.evaluate();
1275		lhs == rhs
1276	}
1277
1278	// Monad Laws
1279
1280	/// Monad left identity: `pure(a).bind(f) == f(a)`.
1281	#[quickcheck]
1282	fn monad_left_identity(a: i32) -> bool {
1283		let f = |x: i32| pure::<ThunkBrand, _>(x.wrapping_mul(2));
1284		let lhs = bind::<ThunkBrand, _, _>(pure::<ThunkBrand, _>(a), f).evaluate();
1285		let rhs = f(a).evaluate();
1286		lhs == rhs
1287	}
1288
1289	/// Monad right identity: `m.bind(pure) == m`.
1290	#[quickcheck]
1291	fn monad_right_identity(x: i32) -> bool {
1292		let lhs =
1293			bind::<ThunkBrand, _, _>(pure::<ThunkBrand, _>(x), pure::<ThunkBrand, _>).evaluate();
1294		lhs == x
1295	}
1296
1297	/// Monad associativity: `m.bind(f).bind(g) == m.bind(|a| f(a).bind(g))`.
1298	#[quickcheck]
1299	fn monad_associativity(x: i32) -> bool {
1300		let f = |a: i32| pure::<ThunkBrand, _>(a.wrapping_add(1));
1301		let g = |a: i32| pure::<ThunkBrand, _>(a.wrapping_mul(3));
1302		let m = pure::<ThunkBrand, _>(x);
1303		let m2 = pure::<ThunkBrand, _>(x);
1304		let lhs = bind::<ThunkBrand, _, _>(bind::<ThunkBrand, _, _>(m, f), g).evaluate();
1305		let rhs =
1306			bind::<ThunkBrand, _, _>(m2, move |a| bind::<ThunkBrand, _, _>(f(a), g)).evaluate();
1307		lhs == rhs
1308	}
1309
1310	// Semigroup Laws
1311
1312	/// Semigroup associativity: `append(append(a, b), c) == append(a, append(b, c))`.
1313	#[quickcheck]
1314	fn semigroup_associativity(
1315		a: String,
1316		b: String,
1317		c: String,
1318	) -> bool {
1319		let ta = pure::<ThunkBrand, _>(a.clone());
1320		let tb = pure::<ThunkBrand, _>(b.clone());
1321		let tc = pure::<ThunkBrand, _>(c.clone());
1322		let ta2 = pure::<ThunkBrand, _>(a);
1323		let tb2 = pure::<ThunkBrand, _>(b);
1324		let tc2 = pure::<ThunkBrand, _>(c);
1325		let lhs = append(append(ta, tb), tc).evaluate();
1326		let rhs = append(ta2, append(tb2, tc2)).evaluate();
1327		lhs == rhs
1328	}
1329
1330	// Monoid Laws
1331
1332	/// Monoid left identity: `append(empty(), a) == a`.
1333	#[quickcheck]
1334	fn monoid_left_identity(x: String) -> bool {
1335		let a = pure::<ThunkBrand, _>(x.clone());
1336		let lhs: Thunk<String> = append(empty(), a);
1337		lhs.evaluate() == x
1338	}
1339
1340	/// Monoid right identity: `append(a, empty()) == a`.
1341	#[quickcheck]
1342	fn monoid_right_identity(x: String) -> bool {
1343		let a = pure::<ThunkBrand, _>(x.clone());
1344		let rhs: Thunk<String> = append(a, empty());
1345		rhs.evaluate() == x
1346	}
1347
1348	// 7.1: HKT-level trait tests
1349
1350	/// Tests `Foldable` for `ThunkBrand` via the free function `fold_right`.
1351	#[test]
1352	fn test_foldable_via_brand() {
1353		let thunk = pure::<ThunkBrand, _>(10);
1354		let result = fold_right::<RcFnBrand, ThunkBrand, _, _>(|x, acc| x + acc, 5, thunk);
1355		assert_eq!(result, 15);
1356	}
1357
1358	/// Tests `Lift::lift2` for `ThunkBrand` via the free function.
1359	#[test]
1360	fn test_lift2_via_brand() {
1361		let t1 = pure::<ThunkBrand, _>(10);
1362		let t2 = pure::<ThunkBrand, _>(20);
1363		let result = lift2::<ThunkBrand, _, _, _>(|a, b| a + b, t1, t2);
1364		assert_eq!(result.evaluate(), 30);
1365	}
1366
1367	/// Tests `Semiapplicative::apply` for `ThunkBrand` via the free function.
1368	#[test]
1369	fn test_apply_via_brand() {
1370		let func = pure::<ThunkBrand, _>(cloneable_fn_new::<RcFnBrand, _, _>(|x: i32| x * 2));
1371		let val = pure::<ThunkBrand, _>(21);
1372		let result = apply::<RcFnBrand, ThunkBrand, _, _>(func, val);
1373		assert_eq!(result.evaluate(), 42);
1374	}
1375
1376	/// Tests `Extract::extract` for `ThunkBrand` via the free function.
1377	#[test]
1378	fn test_extract_via_brand() {
1379		let thunk = pure::<ThunkBrand, _>(42);
1380		let result = extract::<ThunkBrand, _>(thunk);
1381		assert_eq!(result, 42);
1382	}
1383
1384	// 7.2: into_rc_lazy and into_arc_lazy tests
1385
1386	/// Tests that `Thunk::into_rc_lazy` caches the result and does not re-run the closure.
1387	#[test]
1388	fn test_memoize_caching() {
1389		use std::cell::Cell;
1390
1391		let counter = Cell::new(0usize);
1392		let thunk = Thunk::new(|| {
1393			counter.set(counter.get() + 1);
1394			42
1395		});
1396		let lazy = thunk.into_rc_lazy();
1397
1398		assert_eq!(counter.get(), 0);
1399		assert_eq!(*lazy.evaluate(), 42);
1400		assert_eq!(counter.get(), 1);
1401		assert_eq!(*lazy.evaluate(), 42);
1402		assert_eq!(counter.get(), 1);
1403	}
1404
1405	/// Tests that `Thunk::into_arc_lazy` caches the result and does not re-run the closure.
1406	#[test]
1407	fn test_memoize_arc_caching() {
1408		use std::sync::atomic::{
1409			AtomicUsize,
1410			Ordering,
1411		};
1412
1413		let counter = AtomicUsize::new(0);
1414		let thunk = Thunk::new(|| {
1415			counter.fetch_add(1, Ordering::SeqCst);
1416			42
1417		});
1418		let lazy = thunk.into_arc_lazy();
1419
1420		// into_arc_lazy evaluates eagerly because Thunk is !Send,
1421		// so the counter should already be 1.
1422		assert_eq!(counter.load(Ordering::SeqCst), 1);
1423		assert_eq!(*lazy.evaluate(), 42);
1424		assert_eq!(counter.load(Ordering::SeqCst), 1);
1425		assert_eq!(*lazy.evaluate(), 42);
1426		assert_eq!(counter.load(Ordering::SeqCst), 1);
1427	}
1428
1429	/// Tests `MonadRec::tail_rec_m` stack safety with a large iteration count.
1430	///
1431	/// Verifies that `tail_rec_m` does not overflow the stack even with 100,000+ iterations,
1432	/// because it uses an iterative loop internally rather than recursive calls.
1433	#[test]
1434	fn test_tail_rec_m_stack_safety() {
1435		use {
1436			crate::{
1437				brands::ThunkBrand,
1438				classes::monad_rec::tail_rec_m,
1439				functions::pure,
1440			},
1441			core::ops::ControlFlow,
1442		};
1443
1444		let iterations: i64 = 200_000;
1445		let result = tail_rec_m::<ThunkBrand, _, _>(
1446			|acc| {
1447				pure::<ThunkBrand, _>(
1448					if acc < iterations {
1449						ControlFlow::Continue(acc + 1)
1450					} else {
1451						ControlFlow::Break(acc)
1452					},
1453				)
1454			},
1455			0i64,
1456		);
1457		assert_eq!(result.evaluate(), iterations);
1458	}
1459
1460	/// Tests `FunctorWithIndex` for `ThunkBrand` via the HKT trait method.
1461	///
1462	/// Verifies that `map_with_index` provides the unit index `()` and transforms the value.
1463	#[test]
1464	fn test_functor_with_index() {
1465		use crate::{
1466			brands::ThunkBrand,
1467			classes::functor_with_index::FunctorWithIndex,
1468			functions::pure,
1469		};
1470
1471		let thunk = pure::<ThunkBrand, _>(21);
1472		let result = ThunkBrand::map_with_index(|(), x| x * 2, thunk);
1473		assert_eq!(result.evaluate(), 42);
1474	}
1475
1476	/// Tests `FunctorWithIndex` identity law for `ThunkBrand`.
1477	///
1478	/// Verifies that `map_with_index(|_, a| a, fa)` is equivalent to `fa`.
1479	#[test]
1480	fn test_functor_with_index_identity() {
1481		use crate::{
1482			brands::ThunkBrand,
1483			classes::functor_with_index::FunctorWithIndex,
1484			functions::pure,
1485		};
1486
1487		let thunk = pure::<ThunkBrand, _>(42);
1488		let result = ThunkBrand::map_with_index(|_, a: i32| a, thunk);
1489		assert_eq!(result.evaluate(), 42);
1490	}
1491
1492	/// Tests `FunctorWithIndex` compatibility with `Functor` for `ThunkBrand`.
1493	///
1494	/// Verifies that `map(f, fa) == map_with_index(|_, a| f(a), fa)`.
1495	#[test]
1496	fn test_functor_with_index_compat_with_functor() {
1497		use crate::{
1498			brands::ThunkBrand,
1499			classes::functor_with_index::FunctorWithIndex,
1500			functions::{
1501				map,
1502				pure,
1503			},
1504		};
1505
1506		let f = |a: i32| a * 3 + 1;
1507		let thunk1 = pure::<ThunkBrand, _>(10);
1508		let thunk2 = pure::<ThunkBrand, _>(10);
1509		let via_map = map::<ThunkBrand, _, _>(f, thunk1).evaluate();
1510		let via_map_with_index = ThunkBrand::map_with_index(|_, a| f(a), thunk2).evaluate();
1511		assert_eq!(via_map, via_map_with_index);
1512	}
1513
1514	/// Tests `FoldableWithIndex` for `ThunkBrand` via the HKT trait method.
1515	///
1516	/// Verifies that `fold_map_with_index` provides the unit index `()` and folds the value.
1517	#[test]
1518	fn test_foldable_with_index() {
1519		use crate::{
1520			brands::ThunkBrand,
1521			classes::foldable_with_index::FoldableWithIndex,
1522			functions::pure,
1523		};
1524
1525		let thunk = pure::<ThunkBrand, _>(42);
1526		let result: String = ThunkBrand::fold_map_with_index(|(), a: i32| a.to_string(), thunk);
1527		assert_eq!(result, "42");
1528	}
1529
1530	/// Tests `FoldableWithIndex` compatibility with `Foldable` for `ThunkBrand`.
1531	///
1532	/// Verifies that `fold_map(f, fa) == fold_map_with_index(|_, a| f(a), fa)`.
1533	#[test]
1534	fn test_foldable_with_index_compat_with_foldable() {
1535		use crate::{
1536			brands::*,
1537			classes::foldable_with_index::FoldableWithIndex,
1538			functions::{
1539				fold_map,
1540				pure,
1541			},
1542		};
1543
1544		let f = |a: i32| a.to_string();
1545		let thunk1 = pure::<ThunkBrand, _>(99);
1546		let thunk2 = pure::<ThunkBrand, _>(99);
1547		let via_fold_map = fold_map::<RcFnBrand, ThunkBrand, _, _>(f, thunk1);
1548		let via_fold_map_with_index: String = ThunkBrand::fold_map_with_index(|_, a| f(a), thunk2);
1549		assert_eq!(via_fold_map, via_fold_map_with_index);
1550	}
1551
1552	// Extract / Extend / Comonad Laws
1553
1554	/// Extract pure-extract law: `extract(pure(x)) == x`.
1555	#[quickcheck]
1556	fn extract_pure(x: i32) -> bool {
1557		extract::<ThunkBrand, _>(pure::<ThunkBrand, _>(x)) == x
1558	}
1559
1560	/// Comonad left identity: `extract(extend(f, wa)) == f(wa)`.
1561	#[quickcheck]
1562	fn comonad_left_identity(x: i32) -> bool {
1563		use crate::classes::extend::extend;
1564		let f = |w: Thunk<i32>| w.evaluate().wrapping_mul(3);
1565		let wa = pure::<ThunkBrand, _>(x);
1566		let wa2 = pure::<ThunkBrand, _>(x);
1567		extract::<ThunkBrand, _>(extend::<ThunkBrand, _, _>(f, wa)) == f(wa2)
1568	}
1569
1570	/// Comonad right identity: `extend(extract, wa)` produces the same value as `wa`.
1571	#[quickcheck]
1572	fn comonad_right_identity(x: i32) -> bool {
1573		use crate::classes::extend::extend;
1574		let wa = pure::<ThunkBrand, _>(x);
1575		extract::<ThunkBrand, _>(extend::<ThunkBrand, _, _>(extract::<ThunkBrand, _>, wa)) == x
1576	}
1577
1578	/// Extend associativity: `extend(f, extend(g, w))` equals
1579	/// `extend(|w| f(extend(g, w)), w)`.
1580	#[quickcheck]
1581	fn extend_associativity(x: i32) -> bool {
1582		use crate::classes::extend::extend;
1583		let g = |w: Thunk<i32>| w.evaluate().wrapping_mul(2);
1584		let f = |w: Thunk<i32>| w.evaluate().wrapping_add(1);
1585		let lhs = extract::<ThunkBrand, _>(extend::<ThunkBrand, _, _>(
1586			f,
1587			extend::<ThunkBrand, _, _>(g, pure::<ThunkBrand, _>(x)),
1588		));
1589		let rhs = extract::<ThunkBrand, _>(extend::<ThunkBrand, _, _>(
1590			|w: Thunk<i32>| f(extend::<ThunkBrand, _, _>(g, w)),
1591			pure::<ThunkBrand, _>(x),
1592		));
1593		lhs == rhs
1594	}
1595
1596	/// Tests basic `extend` on `Thunk`.
1597	#[test]
1598	fn extend_test() {
1599		use crate::classes::extend::extend;
1600		let thunk = Thunk::new(|| 21);
1601		let result = extend::<ThunkBrand, _, _>(|w: Thunk<i32>| w.evaluate() * 2, thunk);
1602		assert_eq!(result.evaluate(), 42);
1603	}
1604
1605	/// Comonad map-extract law: extract(map(f, fa)) == f(extract(fa)).
1606	#[quickcheck]
1607	fn comonad_map_extract(x: i32) -> bool {
1608		let f = |a: i32| a.wrapping_mul(3).wrapping_add(7);
1609		let fa = Thunk::new(|| x);
1610		let fa2 = Thunk::new(|| x);
1611		let lhs = extract::<ThunkBrand, _>(map::<ThunkBrand, _, _>(f, fa));
1612		let rhs = f(extract::<ThunkBrand, _>(fa2));
1613		lhs == rhs
1614	}
1615}