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