Skip to main content

fp_library/types/
trampoline.rs

1//! Stack-safe computation type with guaranteed safety for unlimited recursion depth.
2//!
3//! Trampolining converts stack-based recursion into heap-based iteration: instead
4//! of each recursive call consuming a stack frame, each step returns a thunk that
5//! the driver loop evaluates iteratively. This eliminates the risk of stack overflow
6//! regardless of recursion depth.
7//!
8//! Built on the [`Free`](crate::types::Free) monad with O(1) [`bind`](Trampoline::bind) operations. Provides complete stack safety at the cost of requiring `'static` types. Use this for deep recursion and heavy monadic pipelines.
9//!
10//! ### Examples
11//!
12//! ```
13//! use fp_library::types::*;
14//!
15//! let task = Trampoline::new(|| 1 + 1)
16//! 	.bind(|x| Trampoline::new(move || x * 2))
17//! 	.bind(|x| Trampoline::new(move || x + 10));
18//!
19//! assert_eq!(task.evaluate(), 14);
20//! ```
21
22#[fp_macros::document_module]
23mod inner {
24	use {
25		crate::{
26			brands::ThunkBrand,
27			classes::{
28				Deferrable,
29				LazyConfig,
30				Monoid,
31				Semigroup,
32			},
33			types::{
34				ArcLazyConfig,
35				Free,
36				Lazy,
37				RcLazyConfig,
38				Thunk,
39			},
40		},
41		core::ops::ControlFlow,
42		fp_macros::*,
43		std::fmt,
44	};
45
46	/// A lazy, stack-safe computation that produces a value of type `A`.
47	///
48	/// `Trampoline` is the "heavy-duty" monadic type for deferred computations that
49	/// require **guaranteed stack safety**. It is built on [`Free<Thunk, A>`] with
50	/// [`CatList`](crate::types::CatList)-based bind stack, ensuring O(1) [`bind`](Trampoline::bind)
51	/// operations and unlimited recursion depth without stack overflow.
52	///
53	/// # Requirements
54	///
55	/// - `A: 'static` - Required due to type erasure via [`Box<dyn Any>`].
56	///
57	/// # Guarantees
58	///
59	/// - **Stack safe**: Will not overflow regardless of recursion depth.
60	/// - **O(1) bind**: Left-associated `bind` chains don't degrade.
61	/// - **Lazy**: Computation is deferred until [`Trampoline::evaluate`] is called.
62	///
63	/// # When to Use `Trampoline` vs [`Thunk`]
64	///
65	/// - Use **`Trampoline<A>`** for deep recursion, heavy monadic pipelines.
66	/// - Use **`Thunk<'a, A>`** for HKT integration, borrowed references, glue code.
67	///
68	/// # Memoization
69	///
70	/// `Trampoline` does NOT memoize. Each call to `evaluate` re-evaluates.
71	/// For memoization, wrap in [`Lazy`]:
72	///
73	/// ```rust
74	/// use fp_library::types::*;
75	///
76	/// let lazy: Lazy<i32> = Lazy::<_, RcLazyConfig>::new(|| Trampoline::new(|| 1 + 1).evaluate());
77	/// lazy.evaluate(); // Computes
78	/// lazy.evaluate(); // Returns cached
79	/// ```
80	///
81	/// # Drop behavior
82	///
83	/// Dropping a `Trampoline` dismantles its inner [`Free<ThunkBrand, A>`](Free)
84	/// chain iteratively. Each suspended thunk in the chain is evaluated during drop
85	/// to access the next node. Be aware that dropping a partially-evaluated
86	/// `Trampoline` may trigger deferred computations.
87	#[document_type_parameters("The type of the value produced by the task.")]
88	///
89	pub struct Trampoline<A: 'static>(
90		/// The internal `Free` monad representation.
91		Free<ThunkBrand, A>,
92	);
93
94	#[document_type_parameters("The type of the value produced by the task.")]
95	#[document_parameters("The `Trampoline` instance.")]
96	impl<A: 'static> Trampoline<A> {
97		/// Creates a `Trampoline` from an already-computed value.
98		///
99		/// ### Complexity
100		///
101		/// O(1) creation, O(1) evaluation
102		#[document_signature]
103		///
104		#[document_parameters("The value to wrap.")]
105		///
106		#[document_returns("A `Trampoline` that produces the value `a`.")]
107		///
108		#[inline]
109		#[document_examples]
110		///
111		/// ```
112		/// use fp_library::types::*;
113		///
114		/// let task = Trampoline::pure(42);
115		/// assert_eq!(task.evaluate(), 42);
116		/// ```
117		pub fn pure(a: A) -> Self {
118			Trampoline(Free::pure(a))
119		}
120
121		/// Creates a lazy `Trampoline` that computes `f` on first evaluation.
122		///
123		/// `Trampoline` does NOT memoize - each `evaluate()`
124		/// re-evaluates. Use [`Lazy`] for caching.
125		///
126		/// # Complexity
127		/// O(1) creation
128		#[document_signature]
129		///
130		#[document_parameters("The closure to execute.")]
131		///
132		#[document_returns("A `Trampoline` that executes `f` when run.")]
133		///
134		#[inline]
135		#[document_examples]
136		///
137		/// ```
138		/// use fp_library::types::*;
139		///
140		/// let task = Trampoline::new(|| {
141		/// 	// println!("Computing!");
142		/// 	1 + 1
143		/// });
144		///
145		/// // Nothing computed yet
146		/// let result = task.evaluate(); // Now the closure runs
147		/// assert_eq!(result, 2);
148		/// ```
149		pub fn new(f: impl FnOnce() -> A + 'static) -> Self {
150			Trampoline(Free::wrap(Thunk::new(move || Free::pure(f()))))
151		}
152
153		/// Defers the construction of a `Trampoline` itself.
154		///
155		/// This is critical for stack-safe recursion: instead of
156		/// building a chain of `Trampoline`s directly (which grows the stack),
157		/// we defer the construction.
158		#[document_signature]
159		///
160		#[document_parameters("The closure that produces a `Trampoline`.")]
161		///
162		#[document_returns("A `Trampoline` that defers the creation of the inner task.")]
163		///
164		#[inline]
165		#[document_examples]
166		///
167		/// ```
168		/// use fp_library::types::*;
169		///
170		/// fn recursive_sum(
171		/// 	n: u64,
172		/// 	acc: u64,
173		/// ) -> Trampoline<u64> {
174		/// 	if n == 0 {
175		/// 		Trampoline::pure(acc)
176		/// 	} else {
177		/// 		// Defer construction to avoid stack growth
178		/// 		Trampoline::defer(move || recursive_sum(n - 1, acc + n))
179		/// 	}
180		/// }
181		///
182		/// // This works for n = 1_000_000 without stack overflow!
183		/// let result = recursive_sum(1_000, 0).evaluate();
184		/// assert_eq!(result, 500500);
185		/// ```
186		pub fn defer(f: impl FnOnce() -> Trampoline<A> + 'static) -> Self {
187			Trampoline(Free::wrap(Thunk::new(move || f().0)))
188		}
189
190		/// Monadic bind with O(1) complexity.
191		///
192		/// Chains computations together. The key property is that
193		/// left-associated chains don't degrade to O(n²).
194		#[document_signature]
195		///
196		#[document_type_parameters("The type of the result of the new task.")]
197		///
198		#[document_parameters("The function to apply to the result of this task.")]
199		///
200		#[document_returns("A new `Trampoline` that chains `f` after this task.")]
201		///
202		#[inline]
203		#[document_examples]
204		///
205		/// ```
206		/// use fp_library::types::*;
207		///
208		/// // This is O(n), not O(n²)
209		/// let mut task = Trampoline::pure(0);
210		/// for i in 0 .. 100 {
211		/// 	task = task.bind(move |x| Trampoline::pure(x + i));
212		/// }
213		/// assert_eq!(task.evaluate(), 4950);
214		/// ```
215		pub fn bind<B: 'static>(
216			self,
217			f: impl FnOnce(A) -> Trampoline<B> + 'static,
218		) -> Trampoline<B> {
219			Trampoline(self.0.bind(move |a| f(a).0))
220		}
221
222		/// Functor map: transforms the result without changing structure.
223		#[document_signature]
224		///
225		#[document_type_parameters("The type of the result of the mapping function.")]
226		///
227		#[document_parameters("The function to apply to the result of this task.")]
228		///
229		#[document_returns("A new `Trampoline` with the transformed result.")]
230		///
231		#[inline]
232		#[document_examples]
233		///
234		/// ```
235		/// use fp_library::types::*;
236		///
237		/// let task = Trampoline::pure(10).map(|x| x * 2);
238		/// assert_eq!(task.evaluate(), 20);
239		/// ```
240		pub fn map<B: 'static>(
241			self,
242			f: impl FnOnce(A) -> B + 'static,
243		) -> Trampoline<B> {
244			Trampoline(self.0.map(f))
245		}
246
247		/// Forces evaluation and returns the result.
248		///
249		/// This runs the trampoline loop, iteratively processing
250		/// the CatList of continuations without growing the stack.
251		#[document_signature]
252		///
253		#[document_parameters]
254		///
255		#[document_returns("The result of the computation.")]
256		///
257		#[document_examples]
258		///
259		/// ```
260		/// use fp_library::types::*;
261		///
262		/// let task = Trampoline::new(|| 1 + 1);
263		/// assert_eq!(task.evaluate(), 2);
264		/// ```
265		pub fn evaluate(self) -> A {
266			self.0.evaluate()
267		}
268
269		/// Converts this `Trampoline` into a memoized [`Lazy`](crate::types::Lazy) value.
270		///
271		/// The computation will be evaluated at most once; subsequent accesses
272		/// return the cached result.
273		#[document_signature]
274		///
275		#[document_returns(
276			"A memoized `Lazy` value that evaluates this trampoline on first access."
277		)]
278		///
279		#[document_examples]
280		///
281		/// ```
282		/// use fp_library::types::*;
283		///
284		/// let task = Trampoline::new(|| 42);
285		/// let lazy = task.into_rc_lazy();
286		/// // evaluate() returns &i32, so deref to get i32 for comparison
287		/// assert_eq!(*lazy.evaluate(), 42);
288		/// ```
289		pub fn into_rc_lazy(self) -> Lazy<'static, A, RcLazyConfig> {
290			Lazy::from(self)
291		}
292
293		/// Evaluates this `Trampoline` and wraps the result in a thread-safe [`ArcLazy`](crate::types::Lazy).
294		///
295		/// The trampoline is evaluated eagerly because its inner closures are
296		/// `!Send` (they are stored as `Box<dyn FnOnce>` inside the underlying
297		/// `Free` monad), so they cannot be placed inside an `Arc`-based lazy
298		/// value that requires `Send`. By evaluating first, only the resulting
299		/// `A` (which is `Send + Sync`) needs to cross the thread-safety boundary.
300		#[document_signature]
301		///
302		#[document_returns("A thread-safe `ArcLazy` containing the eagerly evaluated result.")]
303		///
304		#[document_examples]
305		///
306		/// ```
307		/// use fp_library::types::*;
308		///
309		/// let task = Trampoline::new(|| 42);
310		/// let lazy = task.into_arc_lazy();
311		/// assert_eq!(*lazy.evaluate(), 42);
312		/// ```
313		pub fn into_arc_lazy(self) -> Lazy<'static, A, ArcLazyConfig>
314		where
315			A: Send + Sync, {
316			Lazy::from(self)
317		}
318
319		/// Combines two `Trampoline`s, running both and combining results.
320		#[document_signature]
321		///
322		#[document_type_parameters(
323			"The type of the second task's result.",
324			"The type of the combined result."
325		)]
326		///
327		#[document_parameters("The second task.", "The function to combine the results.")]
328		///
329		#[document_returns("A new `Trampoline` producing the combined result.")]
330		///
331		#[document_examples]
332		///
333		/// ```
334		/// use fp_library::types::*;
335		///
336		/// let t1 = Trampoline::pure(10);
337		/// let t2 = Trampoline::pure(20);
338		/// let t3 = t1.lift2(t2, |a, b| a + b);
339		/// assert_eq!(t3.evaluate(), 30);
340		/// ```
341		pub fn lift2<B: 'static, C: 'static>(
342			self,
343			other: Trampoline<B>,
344			f: impl FnOnce(A, B) -> C + 'static,
345		) -> Trampoline<C> {
346			self.bind(move |a| other.map(move |b| f(a, b)))
347		}
348
349		/// Sequences two `Trampoline`s, discarding the first result.
350		#[document_signature]
351		///
352		#[document_type_parameters("The type of the second task's result.")]
353		///
354		#[document_parameters("The second task.")]
355		///
356		#[document_returns(
357			"A new `Trampoline` that runs both tasks and returns the result of the second."
358		)]
359		///
360		#[document_examples]
361		///
362		/// ```
363		/// use fp_library::types::*;
364		///
365		/// let t1 = Trampoline::pure(10);
366		/// let t2 = Trampoline::pure(20);
367		/// let t3 = t1.then(t2);
368		/// assert_eq!(t3.evaluate(), 20);
369		/// ```
370		pub fn then<B: 'static>(
371			self,
372			other: Trampoline<B>,
373		) -> Trampoline<B> {
374			self.bind(move |_| other)
375		}
376
377		/// Combines two `Trampoline` values using the `Semigroup` operation on their results.
378		///
379		/// Evaluates both trampolines and combines the results via [`Semigroup::append`].
380		/// The combination itself is deferred and stack-safe.
381		#[document_signature]
382		///
383		#[document_parameters(
384			"The second `Trampoline` whose result will be combined with this one."
385		)]
386		///
387		#[document_returns("A new `Trampoline` producing the combined result.")]
388		///
389		#[document_examples]
390		///
391		/// ```
392		/// use fp_library::types::*;
393		///
394		/// let t1 = Trampoline::pure(vec![1, 2]);
395		/// let t2 = Trampoline::pure(vec![3, 4]);
396		/// assert_eq!(t1.append(t2).evaluate(), vec![1, 2, 3, 4]);
397		/// ```
398		#[inline]
399		pub fn append(
400			self,
401			other: Trampoline<A>,
402		) -> Trampoline<A>
403		where
404			A: Semigroup + 'static, {
405			self.lift2(other, Semigroup::append)
406		}
407
408		/// Creates a `Trampoline` that produces the identity element for the given `Monoid`.
409		#[document_signature]
410		///
411		#[document_returns("A `Trampoline` producing the monoid identity element.")]
412		///
413		#[document_examples]
414		///
415		/// ```
416		/// use fp_library::types::*;
417		///
418		/// let t: Trampoline<Vec<i32>> = Trampoline::empty();
419		/// assert_eq!(t.evaluate(), Vec::<i32>::new());
420		/// ```
421		#[inline]
422		pub fn empty() -> Trampoline<A>
423		where
424			A: Monoid + 'static, {
425			Trampoline::pure(Monoid::empty())
426		}
427
428		/// Stack-safe tail recursion within Trampoline.
429		///
430		/// # Clone Bound
431		///
432		/// The function `f` must implement `Clone` because each iteration
433		/// of the recursion may need its own copy. Most closures naturally
434		/// implement `Clone` when all their captures implement `Clone`.
435		///
436		/// For closures that don't implement `Clone`, use `arc_tail_rec_m`
437		/// which wraps the closure in `Arc` internally.
438		#[document_signature]
439		///
440		#[document_type_parameters("The type of the state.")]
441		///
442		#[document_parameters(
443			"The function that performs one step of the recursion.",
444			"The initial state."
445		)]
446		///
447		#[document_returns("A `Trampoline` that performs the recursion.")]
448		#[document_examples]
449		///
450		/// ```
451		/// use {
452		/// 	core::ops::ControlFlow,
453		/// 	fp_library::types::Trampoline,
454		/// };
455		///
456		/// // Fibonacci using tail recursion
457		/// fn fib(n: u64) -> Trampoline<u64> {
458		/// 	Trampoline::tail_rec_m(
459		/// 		|(n, a, b)| {
460		/// 			if n == 0 {
461		/// 				Trampoline::pure(ControlFlow::Break(a))
462		/// 			} else {
463		/// 				Trampoline::pure(ControlFlow::Continue((n - 1, b, a + b)))
464		/// 			}
465		/// 		},
466		/// 		(n, 0u64, 1u64),
467		/// 	)
468		/// }
469		///
470		/// assert_eq!(fib(50).evaluate(), 12586269025);
471		/// ```
472		pub fn tail_rec_m<S: 'static>(
473			f: impl Fn(S) -> Trampoline<ControlFlow<A, S>> + Clone + 'static,
474			initial: S,
475		) -> Self {
476			// Use defer to ensure each step is trampolined.
477			fn go<A: 'static, B: 'static, F>(
478				f: F,
479				a: A,
480			) -> Trampoline<B>
481			where
482				F: Fn(A) -> Trampoline<ControlFlow<B, A>> + Clone + 'static, {
483				Trampoline::defer(move || {
484					let result = f(a);
485					result.bind(move |step| match step {
486						ControlFlow::Continue(next) => go(f, next),
487						ControlFlow::Break(b) => Trampoline::pure(b),
488					})
489				})
490			}
491
492			go(f, initial)
493		}
494
495		/// Arc-wrapped version for non-Clone closures.
496		///
497		/// Use this when your closure captures non-Clone state.
498		#[document_signature]
499		///
500		#[document_type_parameters("The type of the state.")]
501		///
502		#[document_parameters(
503			"The function that performs one step of the recursion.",
504			"The initial state."
505		)]
506		///
507		#[document_returns("A `Trampoline` that performs the recursion.")]
508		#[document_examples]
509		///
510		/// ```
511		/// use {
512		/// 	core::ops::ControlFlow,
513		/// 	fp_library::types::Trampoline,
514		/// 	std::sync::{
515		/// 		Arc,
516		/// 		atomic::{
517		/// 			AtomicUsize,
518		/// 			Ordering,
519		/// 		},
520		/// 	},
521		/// };
522		///
523		/// // Closure captures non-Clone state
524		/// let counter = Arc::new(AtomicUsize::new(0));
525		/// let counter_clone = Arc::clone(&counter);
526		/// let task = Trampoline::arc_tail_rec_m(
527		/// 	move |n| {
528		/// 		counter_clone.fetch_add(1, Ordering::SeqCst);
529		/// 		if n == 0 {
530		/// 			Trampoline::pure(ControlFlow::Break(0))
531		/// 		} else {
532		/// 			Trampoline::pure(ControlFlow::Continue(n - 1))
533		/// 		}
534		/// 	},
535		/// 	100,
536		/// );
537		/// assert_eq!(task.evaluate(), 0);
538		/// assert_eq!(counter.load(Ordering::SeqCst), 101);
539		/// ```
540		pub fn arc_tail_rec_m<S: 'static>(
541			f: impl Fn(S) -> Trampoline<ControlFlow<A, S>> + 'static,
542			initial: S,
543		) -> Self {
544			use std::sync::Arc;
545			let f = Arc::new(f);
546			let wrapper = move |s: S| {
547				let f = Arc::clone(&f);
548				f(s)
549			};
550			Self::tail_rec_m(wrapper, initial)
551		}
552
553		/// Peels off one layer of the trampoline.
554		///
555		/// Returns `Ok(a)` if the computation has already completed with value `a`,
556		/// or `Err(thunk)` if the computation is suspended. Evaluating the returned
557		/// [`Thunk`] yields the next `Trampoline` step.
558		///
559		/// This is useful for implementing custom interpreters or drivers that need
560		/// to interleave trampoline steps with other logic (e.g., logging, resource
561		/// cleanup, cooperative scheduling).
562		#[document_signature]
563		///
564		#[document_returns(
565			"`Ok(a)` if the computation is finished, `Err(thunk)` if it is suspended."
566		)]
567		///
568		#[document_examples]
569		///
570		/// ```
571		/// use fp_library::types::*;
572		///
573		/// // A pure trampoline resumes immediately.
574		/// let t = Trampoline::pure(42);
575		/// assert_eq!(t.resume().unwrap(), 42);
576		///
577		/// // A deferred trampoline is suspended.
578		/// let t = Trampoline::defer(|| Trampoline::pure(99));
579		/// match t.resume() {
580		/// 	Ok(_) => panic!("expected suspension"),
581		/// 	Err(thunk) => {
582		/// 		let next = thunk.evaluate();
583		/// 		assert_eq!(next.resume().unwrap(), 99);
584		/// 	}
585		/// }
586		/// ```
587		pub fn resume(self) -> Result<A, Thunk<'static, Trampoline<A>>> {
588			match self.0.resume() {
589				Ok(a) => Ok(a),
590				Err(thunk_of_free) => Err(thunk_of_free.map(Trampoline)),
591			}
592		}
593	}
594
595	#[document_type_parameters(
596		"The type of the value produced by the task.",
597		"The memoization configuration."
598	)]
599	impl<A: 'static + Clone, Config: LazyConfig> From<Lazy<'static, A, Config>> for Trampoline<A> {
600		/// Converts a [`Lazy`] value into a [`Trampoline`] by cloning the memoized value.
601		///
602		/// This conversion clones the cached value on each evaluation.
603		/// The cost depends on the [`Clone`] implementation of `A`.
604		#[document_signature]
605		#[document_parameters("The lazy value to convert.")]
606		#[document_returns("A trampoline that evaluates the lazy value.")]
607		#[document_examples]
608		///
609		/// ```
610		/// use fp_library::types::*;
611		/// let lazy = Lazy::<_, RcLazyConfig>::pure(42);
612		/// let task = Trampoline::from(lazy);
613		/// assert_eq!(task.evaluate(), 42);
614		/// ```
615		fn from(lazy: Lazy<'static, A, Config>) -> Self {
616			Trampoline::new(move || lazy.evaluate().clone())
617		}
618	}
619
620	#[document_type_parameters("The type of the value produced by the task.")]
621	impl<A: 'static> Deferrable<'static> for Trampoline<A> {
622		/// Creates a `Trampoline` from a computation that produces it.
623		#[document_signature]
624		///
625		#[document_parameters("A thunk that produces the trampoline.")]
626		///
627		#[document_returns("The deferred trampoline.")]
628		///
629		#[document_examples]
630		///
631		/// ```
632		/// use fp_library::{
633		/// 	brands::*,
634		/// 	classes::Deferrable,
635		/// 	functions::*,
636		/// 	types::*,
637		/// };
638		///
639		/// let task: Trampoline<i32> = Deferrable::defer(|| Trampoline::pure(42));
640		/// assert_eq!(task.evaluate(), 42);
641		/// ```
642		fn defer(f: impl FnOnce() -> Self + 'static) -> Self
643		where
644			Self: Sized, {
645			Trampoline::defer(f)
646		}
647	}
648
649	#[document_type_parameters("The type of the value produced by the task.")]
650	impl<A: Semigroup + 'static> Semigroup for Trampoline<A> {
651		/// Combines two `Trampoline`s by combining their results via [`Semigroup::append`].
652		#[document_signature]
653		///
654		#[document_parameters("The first `Trampoline`.", "The second `Trampoline`.")]
655		///
656		#[document_returns("A new `Trampoline` producing the combined result.")]
657		///
658		#[document_examples]
659		///
660		/// ```
661		/// use fp_library::{
662		/// 	classes::*,
663		/// 	functions::*,
664		/// 	types::*,
665		/// };
666		///
667		/// let t1 = Trampoline::pure(vec![1, 2]);
668		/// let t2 = Trampoline::pure(vec![3, 4]);
669		/// let t3 = append::<_>(t1, t2);
670		/// assert_eq!(t3.evaluate(), vec![1, 2, 3, 4]);
671		/// ```
672		fn append(
673			a: Self,
674			b: Self,
675		) -> Self {
676			a.lift2(b, Semigroup::append)
677		}
678	}
679
680	#[document_type_parameters("The type of the value produced by the task.")]
681	impl<A: Monoid + 'static> Monoid for Trampoline<A> {
682		/// Returns a `Trampoline` producing the identity element for `A`.
683		#[document_signature]
684		///
685		#[document_returns("A `Trampoline` producing the monoid identity element.")]
686		///
687		#[document_examples]
688		///
689		/// ```
690		/// use fp_library::{
691		/// 	classes::*,
692		/// 	functions::*,
693		/// 	types::*,
694		/// };
695		///
696		/// let t: Trampoline<Vec<i32>> = empty::<Trampoline<Vec<i32>>>();
697		/// assert_eq!(t.evaluate(), Vec::<i32>::new());
698		/// ```
699		fn empty() -> Self {
700			Trampoline::pure(Monoid::empty())
701		}
702	}
703
704	#[document_type_parameters("The type of the value produced by the task.")]
705	#[document_parameters("The trampoline to format.")]
706	impl<A: 'static> fmt::Debug for Trampoline<A> {
707		/// Formats the trampoline without evaluating it.
708		#[document_signature]
709		#[document_parameters("The formatter.")]
710		#[document_returns("The formatting result.")]
711		#[document_examples]
712		///
713		/// ```
714		/// use fp_library::types::*;
715		/// let task = Trampoline::pure(42);
716		/// assert_eq!(format!("{:?}", task), "Trampoline(<unevaluated>)");
717		/// ```
718		fn fmt(
719			&self,
720			f: &mut fmt::Formatter<'_>,
721		) -> fmt::Result {
722			f.write_str("Trampoline(<unevaluated>)")
723		}
724	}
725}
726pub use inner::*;
727
728#[cfg(test)]
729#[expect(
730	clippy::unwrap_used,
731	clippy::panic,
732	reason = "Tests use panicking operations for brevity and clarity"
733)]
734mod tests {
735	use {
736		super::*,
737		core::ops::ControlFlow,
738		quickcheck_macros::quickcheck,
739	};
740
741	/// Tests `Trampoline::pure`.
742	///
743	/// Verifies that `pure` creates a task that returns the value immediately.
744	#[test]
745	fn test_task_pure() {
746		let task = Trampoline::pure(42);
747		assert_eq!(task.evaluate(), 42);
748	}
749
750	/// Tests `Trampoline::new`.
751	///
752	/// Verifies that `new` creates a task that computes the value when run.
753	#[test]
754	fn test_task_new() {
755		let task = Trampoline::new(|| 42);
756		assert_eq!(task.evaluate(), 42);
757	}
758
759	/// Tests `Trampoline::bind`.
760	///
761	/// Verifies that `bind` chains computations correctly.
762	#[test]
763	fn test_task_bind() {
764		let task = Trampoline::pure(10).bind(|x| Trampoline::pure(x * 2));
765		assert_eq!(task.evaluate(), 20);
766	}
767
768	/// Tests `Trampoline::map`.
769	///
770	/// Verifies that `map` transforms the result.
771	#[test]
772	fn test_task_map() {
773		let task = Trampoline::pure(10).map(|x| x * 2);
774		assert_eq!(task.evaluate(), 20);
775	}
776
777	/// Tests `Trampoline::defer`.
778	///
779	/// Verifies that `defer` delays the creation of the task.
780	#[test]
781	fn test_task_defer() {
782		let task = Trampoline::defer(|| Trampoline::pure(42));
783		assert_eq!(task.evaluate(), 42);
784	}
785
786	/// Tests `Trampoline::tail_rec_m`.
787	///
788	/// Verifies that `tail_rec_m` performs tail recursion correctly.
789	#[test]
790	fn test_task_tail_rec_m() {
791		fn factorial(n: u64) -> Trampoline<u64> {
792			Trampoline::tail_rec_m(
793				|(n, acc)| {
794					if n <= 1 {
795						Trampoline::pure(ControlFlow::Break(acc))
796					} else {
797						Trampoline::pure(ControlFlow::Continue((n - 1, n * acc)))
798					}
799				},
800				(n, 1u64),
801			)
802		}
803
804		assert_eq!(factorial(5).evaluate(), 120);
805	}
806
807	/// Tests `Trampoline::lift2`.
808	///
809	/// Verifies that `lift2` combines two tasks.
810	#[test]
811	fn test_task_lift2() {
812		let t1 = Trampoline::pure(10);
813		let t2 = Trampoline::pure(20);
814		let t3 = t1.lift2(t2, |a, b| a + b);
815		assert_eq!(t3.evaluate(), 30);
816	}
817
818	/// Tests `Trampoline::then`.
819	///
820	/// Verifies that `then` sequences two tasks.
821	#[test]
822	fn test_task_then() {
823		let t1 = Trampoline::pure(10);
824		let t2 = Trampoline::pure(20);
825		let t3 = t1.then(t2);
826		assert_eq!(t3.evaluate(), 20);
827	}
828
829	/// Tests `Trampoline::arc_tail_rec_m`.
830	///
831	/// Verifies that `arc_tail_rec_m` works with non-Clone closures.
832	#[test]
833	fn test_task_arc_tail_rec_m() {
834		use std::sync::{
835			Arc,
836			atomic::{
837				AtomicUsize,
838				Ordering,
839			},
840		};
841
842		let counter = Arc::new(AtomicUsize::new(0));
843		let counter_clone = Arc::clone(&counter);
844
845		let task = Trampoline::arc_tail_rec_m(
846			move |n| {
847				counter_clone.fetch_add(1, Ordering::SeqCst);
848				if n == 0 {
849					Trampoline::pure(ControlFlow::Break(0))
850				} else {
851					Trampoline::pure(ControlFlow::Continue(n - 1))
852				}
853			},
854			10,
855		);
856
857		assert_eq!(task.evaluate(), 0);
858		assert_eq!(counter.load(Ordering::SeqCst), 11);
859	}
860
861	/// Tests `Trampoline::from_memo`.
862	///
863	/// Verifies that `From<Lazy>` creates a task that retrieves the memoized value lazily.
864	#[test]
865	fn test_task_from_memo() {
866		use {
867			crate::types::{
868				Lazy,
869				RcLazyConfig,
870			},
871			std::{
872				cell::RefCell,
873				rc::Rc,
874			},
875		};
876
877		let counter = Rc::new(RefCell::new(0));
878		let counter_clone = counter.clone();
879		let memo = Lazy::<_, RcLazyConfig>::new(move || {
880			*counter_clone.borrow_mut() += 1;
881			42
882		});
883
884		let task = Trampoline::from(memo.clone());
885
886		// Should not have computed yet (lazy creation)
887		assert_eq!(*counter.borrow(), 0);
888
889		assert_eq!(task.evaluate(), 42);
890		assert_eq!(*counter.borrow(), 1);
891
892		// Run again, should use cached value
893		let task2 = Trampoline::from(memo);
894		assert_eq!(task2.evaluate(), 42);
895		assert_eq!(*counter.borrow(), 1);
896	}
897
898	/// Tests `Trampoline::from` with `ArcLazy`.
899	#[test]
900	fn test_task_from_arc_memo() {
901		use {
902			crate::types::{
903				ArcLazyConfig,
904				Lazy,
905			},
906			std::sync::{
907				Arc,
908				Mutex,
909			},
910		};
911
912		let counter = Arc::new(Mutex::new(0));
913		let counter_clone = counter.clone();
914		let memo = Lazy::<_, ArcLazyConfig>::new(move || {
915			*counter_clone.lock().unwrap() += 1;
916			42
917		});
918
919		let task = Trampoline::from(memo.clone());
920
921		// Should not have computed yet (lazy creation)
922		assert_eq!(*counter.lock().unwrap(), 0);
923
924		assert_eq!(task.evaluate(), 42);
925		assert_eq!(*counter.lock().unwrap(), 1);
926
927		// Run again, should use cached value
928		let task2 = Trampoline::from(memo);
929		assert_eq!(task2.evaluate(), 42);
930		assert_eq!(*counter.lock().unwrap(), 1);
931	}
932
933	/// Tests `From<Thunk>` for `Trampoline`.
934	///
935	/// Verifies that converting a `Thunk` to a `Trampoline` preserves the computed value.
936	#[test]
937	fn test_task_from_thunk() {
938		use crate::types::Thunk;
939
940		let thunk = Thunk::pure(42);
941		let task = Trampoline::from(thunk);
942		assert_eq!(task.evaluate(), 42);
943	}
944
945	/// Tests roundtrip `Thunk` -> `Trampoline` -> evaluate.
946	///
947	/// Verifies that a lazy thunk is correctly evaluated when converted to a trampoline.
948	#[test]
949	fn test_task_from_thunk_lazy() {
950		use crate::types::Thunk;
951
952		let thunk = Thunk::new(|| 21 * 2);
953		let task = Trampoline::from(thunk);
954		assert_eq!(task.evaluate(), 42);
955	}
956
957	// QuickCheck Law Tests
958
959	// Functor Laws (via inherent methods)
960
961	/// Functor identity: `pure(a).map(identity) == a`.
962	#[quickcheck]
963	fn functor_identity(x: i32) -> bool {
964		Trampoline::pure(x).map(|a| a).evaluate() == x
965	}
966
967	/// Functor composition: `fa.map(f . g) == fa.map(g).map(f)`.
968	#[quickcheck]
969	fn functor_composition(x: i32) -> bool {
970		let f = |a: i32| a.wrapping_add(1);
971		let g = |a: i32| a.wrapping_mul(2);
972		let lhs = Trampoline::pure(x).map(move |a| f(g(a))).evaluate();
973		let rhs = Trampoline::pure(x).map(g).map(f).evaluate();
974		lhs == rhs
975	}
976
977	// Monad Laws (via inherent methods)
978
979	/// Monad left identity: `pure(a).bind(f) == f(a)`.
980	#[quickcheck]
981	fn monad_left_identity(a: i32) -> bool {
982		let f = |x: i32| Trampoline::pure(x.wrapping_mul(2));
983		Trampoline::pure(a).bind(f).evaluate() == f(a).evaluate()
984	}
985
986	/// Monad right identity: `m.bind(pure) == m`.
987	#[quickcheck]
988	fn monad_right_identity(x: i32) -> bool {
989		Trampoline::pure(x).bind(Trampoline::pure).evaluate() == x
990	}
991
992	/// Monad associativity: `m.bind(f).bind(g) == m.bind(|a| f(a).bind(g))`.
993	#[quickcheck]
994	fn monad_associativity(x: i32) -> bool {
995		let f = |a: i32| Trampoline::pure(a.wrapping_add(1));
996		let g = |a: i32| Trampoline::pure(a.wrapping_mul(3));
997		let lhs = Trampoline::pure(x).bind(f).bind(g).evaluate();
998		let rhs = Trampoline::pure(x).bind(move |a| f(a).bind(g)).evaluate();
999		lhs == rhs
1000	}
1001
1002	// Tests for !Send types (Rc)
1003
1004	/// Tests that `Trampoline` works with `Rc<T>`, a `!Send` type.
1005	///
1006	/// This verifies that the `Send` bound relaxation allows single-threaded
1007	/// stack-safe recursion with reference-counted types.
1008	#[test]
1009	fn test_trampoline_with_rc() {
1010		use std::rc::Rc;
1011
1012		let rc_val = Rc::new(42);
1013		let task = Trampoline::pure(rc_val);
1014		let result = task.evaluate();
1015		assert_eq!(*result, 42);
1016	}
1017
1018	/// Tests `Trampoline::bind` with `Rc<T>`.
1019	///
1020	/// Verifies that `bind` works correctly when the value type is `!Send`.
1021	#[test]
1022	fn test_trampoline_bind_with_rc() {
1023		use std::rc::Rc;
1024
1025		let task = Trampoline::pure(Rc::new(10)).bind(|rc| {
1026			let val = *rc;
1027			Trampoline::pure(Rc::new(val * 2))
1028		});
1029		assert_eq!(*task.evaluate(), 20);
1030	}
1031
1032	/// Tests `Trampoline::map` with `Rc<T>`.
1033	///
1034	/// Verifies that `map` works correctly with `!Send` types.
1035	#[test]
1036	fn test_trampoline_map_with_rc() {
1037		use std::rc::Rc;
1038
1039		let task = Trampoline::pure(Rc::new(10)).map(|rc| Rc::new(*rc * 3));
1040		assert_eq!(*task.evaluate(), 30);
1041	}
1042
1043	/// Tests `Trampoline::defer` with `Rc<T>`.
1044	///
1045	/// Verifies that deferred construction works with `!Send` types.
1046	#[test]
1047	fn test_trampoline_defer_with_rc() {
1048		use std::rc::Rc;
1049
1050		let task = Trampoline::defer(|| Trampoline::pure(Rc::new(42)));
1051		assert_eq!(*task.evaluate(), 42);
1052	}
1053
1054	/// Tests `Trampoline::tail_rec_m` with `Rc<T>`.
1055	///
1056	/// Verifies that stack-safe recursion works with `!Send` types.
1057	#[test]
1058	fn test_trampoline_tail_rec_m_with_rc() {
1059		use std::rc::Rc;
1060
1061		let task = Trampoline::tail_rec_m(
1062			|(n, acc): (u64, Rc<u64>)| {
1063				if n == 0 {
1064					Trampoline::pure(ControlFlow::Break(acc))
1065				} else {
1066					Trampoline::pure(ControlFlow::Continue((n - 1, Rc::new(*acc + n))))
1067				}
1068			},
1069			(100u64, Rc::new(0u64)),
1070		);
1071		assert_eq!(*task.evaluate(), 5050);
1072	}
1073
1074	#[test]
1075	fn test_trampoline_append() {
1076		let t1 = Trampoline::pure(vec![1, 2]);
1077		let t2 = Trampoline::pure(vec![3, 4]);
1078		assert_eq!(t1.append(t2).evaluate(), vec![1, 2, 3, 4]);
1079	}
1080
1081	#[test]
1082	fn test_trampoline_append_strings() {
1083		let t1 = Trampoline::pure("hello".to_string());
1084		let t2 = Trampoline::pure(" world".to_string());
1085		assert_eq!(t1.append(t2).evaluate(), "hello world");
1086	}
1087
1088	#[test]
1089	fn test_trampoline_empty() {
1090		let t: Trampoline<Vec<i32>> = Trampoline::empty();
1091		assert_eq!(t.evaluate(), Vec::<i32>::new());
1092	}
1093
1094	#[test]
1095	fn test_trampoline_append_with_empty() {
1096		let t1 = Trampoline::pure(vec![1, 2, 3]);
1097		let t2: Trampoline<Vec<i32>> = Trampoline::empty();
1098		assert_eq!(t1.append(t2).evaluate(), vec![1, 2, 3]);
1099	}
1100
1101	// 7.7: Deeper stack safety stress test for tail_rec_m
1102
1103	/// Stress test for `Trampoline::tail_rec_m` with 100,000+ iterations.
1104	///
1105	/// Verifies that stack safety holds at depths far exceeding typical stack limits.
1106	#[test]
1107	fn test_tail_rec_m_deep_stack_safety() {
1108		let n: u64 = 200_000;
1109		let result = Trampoline::tail_rec_m(
1110			move |acc: u64| {
1111				if acc >= n {
1112					Trampoline::pure(ControlFlow::Break(acc))
1113				} else {
1114					Trampoline::pure(ControlFlow::Continue(acc + 1))
1115				}
1116			},
1117			0u64,
1118		);
1119		assert_eq!(result.evaluate(), n);
1120	}
1121
1122	/// Stress test for `Trampoline::arc_tail_rec_m` with 100,000+ iterations.
1123	///
1124	/// Verifies that the `Arc`-based variant is also stack-safe at high depth.
1125	#[test]
1126	fn test_arc_tail_rec_m_deep_stack_safety() {
1127		let n: u64 = 200_000;
1128		let result = Trampoline::arc_tail_rec_m(
1129			move |acc: u64| {
1130				if acc >= n {
1131					Trampoline::pure(ControlFlow::Break(acc))
1132				} else {
1133					Trampoline::pure(ControlFlow::Continue(acc + 1))
1134				}
1135			},
1136			0u64,
1137		);
1138		assert_eq!(result.evaluate(), n);
1139	}
1140
1141	/// Tests `Trampoline::resume` on a pure value.
1142	///
1143	/// Verifies that resuming a pure trampoline returns `Ok(value)`.
1144	#[test]
1145	fn test_resume_pure() {
1146		let t = Trampoline::pure(42);
1147		assert_eq!(t.resume().unwrap(), 42);
1148	}
1149
1150	/// Tests `Trampoline::resume` on a deferred computation.
1151	///
1152	/// Verifies that resuming a deferred trampoline returns `Err(thunk)`,
1153	/// and evaluating the thunk yields another trampoline that can be resumed.
1154	#[test]
1155	fn test_resume_deferred() {
1156		let t = Trampoline::defer(|| Trampoline::pure(99));
1157		match t.resume() {
1158			Ok(_) => panic!("expected suspension"),
1159			Err(thunk) => {
1160				let next = thunk.evaluate();
1161				assert_eq!(next.resume().unwrap(), 99);
1162			}
1163		}
1164	}
1165
1166	/// Tests that resuming through a chain of deferred steps eventually reaches `Ok`.
1167	///
1168	/// Builds a chain of three deferred steps and manually drives it to completion.
1169	#[test]
1170	fn test_resume_chain_reaches_ok() {
1171		let t =
1172			Trampoline::defer(|| Trampoline::defer(|| Trampoline::defer(|| Trampoline::pure(7))));
1173
1174		let mut current = t;
1175		let mut steps = 0;
1176		loop {
1177			match current.resume() {
1178				Ok(value) => {
1179					assert_eq!(value, 7);
1180					break;
1181				}
1182				Err(thunk) => {
1183					current = thunk.evaluate();
1184					steps += 1;
1185				}
1186			}
1187		}
1188		assert!(steps > 0, "expected at least one suspension step");
1189	}
1190}