Skip to main content

fp_library/types/
trampoline.rs

1//! Stack-safe computation type with guaranteed safety for unlimited recursion depth.
2//!
3//! Built on the [`Free`](crate::types::Free) monad with O(1) [`bind`](crate::functions::bind) operations. Provides complete stack safety at the cost of requiring `'static` types. Use this for deep recursion and heavy monadic pipelines.
4//!
5//! ### Examples
6//!
7//! ```
8//! use fp_library::types::*;
9//!
10//! let task = Trampoline::new(|| 1 + 1)
11//!     .bind(|x| Trampoline::new(move || x * 2))
12//!     .bind(|x| Trampoline::new(move || x + 10));
13//!
14//! assert_eq!(task.evaluate(), 14);
15//! ```
16
17#[fp_macros::document_module]
18mod inner {
19	use crate::{
20		brands::ThunkBrand,
21		classes::Deferrable,
22		types::{Free, Lazy, LazyConfig, Step, Thunk},
23	};
24	use fp_macros::{document_fields, document_parameters, document_type_parameters};
25
26	/// A lazy, stack-safe computation that produces a value of type `A`.
27	///
28	/// `Trampoline` is the "heavy-duty" monadic type for deferred computations that
29	/// require **guaranteed stack safety**. It is built on [`Free<Thunk, A>`] with
30	/// [`CatList`](crate::types::CatList)-based bind stack, ensuring O(1) [`bind`](crate::functions::bind)
31	/// operations and unlimited recursion depth without stack overflow.
32	///
33	/// # Requirements
34	///
35	/// - `A: 'static + Send` — Required due to type erasure via [`Box<dyn Any>`].
36	///
37	/// # Guarantees
38	///
39	/// - **Stack safe**: Will not overflow regardless of recursion depth.
40	/// - **O(1) bind**: Left-associated `bind` chains don't degrade.
41	/// - **Lazy**: Computation is deferred until [`Trampoline::evaluate`] is called.
42	///
43	/// # When to Use `Trampoline` vs [`Thunk`]
44	///
45	/// - Use **`Trampoline<A>`** for deep recursion, heavy monadic pipelines.
46	/// - Use **`Thunk<'a, A>`** for HKT integration, borrowed references, glue code.
47	///
48	/// # Memoization
49	///
50	/// `Trampoline` does NOT memoize. Each call to `run` re-evaluates.
51	/// For memoization, wrap in [`Lazy`]:
52	///
53	/// ```rust
54	/// use fp_library::types::*;
55	///
56	/// let lazy: Lazy<i32> = Lazy::<_, RcLazyConfig>::new(|| Trampoline::new(|| 1 + 1).evaluate());
57	/// lazy.evaluate(); // Computes
58	/// lazy.evaluate(); // Returns cached
59	/// ```
60	///
61	/// ### Type Parameters
62	///
63	#[document_type_parameters("The type of the value produced by the task.")]
64	///
65	/// ### Fields
66	///
67	#[document_fields("The internal `Free` monad representation.")]
68	///
69	/// ### Examples
70	///
71	/// ```
72	/// use fp_library::types::*;
73	///
74	/// let task = Trampoline::new(|| 1 + 1)
75	///     .bind(|x| Trampoline::new(move || x * 2))
76	///     .bind(|x| Trampoline::new(move || x + 10));
77	///
78	/// assert_eq!(task.evaluate(), 14);
79	/// ```
80	pub struct Trampoline<A: 'static>(Free<ThunkBrand, A>);
81
82	/// ### Type Parameters
83	///
84	#[document_type_parameters("The type of the value produced by the task.")]
85	#[document_parameters("The `Trampoline` instance.")]
86	impl<A: 'static + Send> Trampoline<A> {
87		/// Creates a `Trampoline` from an already-computed value.
88		///
89		/// ### Complexity
90		///
91		/// O(1) creation, O(1) evaluation
92		///
93		/// ### Type Signature
94		///
95		#[document_signature]
96		///
97		/// ### Parameters
98		///
99		#[document_parameters("The value to wrap.")]
100		///
101		/// ### Returns
102		///
103		/// A `Trampoline` that produces the value `a`.
104		///
105		/// ### Examples
106		///
107		/// ```
108		/// use fp_library::types::*;
109		///
110		/// let task = Trampoline::pure(42);
111		/// assert_eq!(task.evaluate(), 42);
112		/// ```
113		#[inline]
114		pub fn pure(a: A) -> Self {
115			Trampoline(Free::pure(a))
116		}
117
118		/// Creates a lazy `Trampoline` that computes `f` on first evaluation.
119		///
120		/// `Trampoline` does NOT memoize — each `evaluate()`
121		/// re-evaluates. Use [`Lazy`] for caching.
122		///
123		/// # Complexity
124		/// O(1) creation
125		///
126		/// ### Type Signature
127		///
128		#[document_signature]
129		///
130		/// ### Type Parameters
131		///
132		#[document_type_parameters("The type of the closure.")]
133		///
134		/// ### Parameters
135		///
136		#[document_parameters("The closure to execute.")]
137		///
138		/// ### Returns
139		///
140		/// A `Trampoline` that executes `f` when run.
141		///
142		/// ### Examples
143		///
144		/// ```
145		/// use fp_library::types::*;
146		///
147		/// let task = Trampoline::new(|| {
148		///     // println!("Computing!");
149		///     1 + 1
150		/// });
151		///
152		/// // Nothing printed yet
153		/// let result = task.evaluate(); // Prints "Computing!"
154		/// ```
155		#[inline]
156		pub fn new<F>(f: F) -> Self
157		where
158			F: FnOnce() -> A + 'static,
159		{
160			Trampoline(Free::wrap(Thunk::new(move || Free::pure(f()))))
161		}
162
163		/// Defers the construction of a `Trampoline` itself.
164		///
165		/// This is critical for stack-safe recursion: instead of
166		/// building a chain of `Trampoline`s directly (which grows the stack),
167		/// we defer the construction.
168		///
169		/// ### Type Signature
170		///
171		#[document_signature]
172		///
173		/// ### Type Parameters
174		///
175		#[document_type_parameters("The type of the closure.")]
176		///
177		/// ### Parameters
178		///
179		#[document_parameters("The closure that produces a `Trampoline`.")]
180		///
181		/// ### Returns
182		///
183		/// A `Trampoline` that defers the creation of the inner task.
184		///
185		/// ### Examples
186		///
187		/// ```
188		/// use fp_library::types::*;
189		///
190		/// fn recursive_sum(n: u64, acc: u64) -> Trampoline<u64> {
191		///     if n == 0 {
192		///         Trampoline::pure(acc)
193		///     } else {
194		///         // Defer construction to avoid stack growth
195		///         Trampoline::defer(move || recursive_sum(n - 1, acc + n))
196		///     }
197		/// }
198		///
199		/// // This works for n = 1_000_000 without stack overflow!
200		/// let result = recursive_sum(1_000, 0).evaluate();
201		/// ```
202		#[inline]
203		pub fn defer<F>(f: F) -> Self
204		where
205			F: FnOnce() -> Trampoline<A> + 'static,
206		{
207			Trampoline(Free::wrap(Thunk::new(move || f().0)))
208		}
209
210		/// Monadic bind with O(1) complexity.
211		///
212		/// Chains computations together. The key property is that
213		/// left-associated chains don't degrade to O(n²).
214		///
215		/// ### Type Signature
216		///
217		#[document_signature]
218		///
219		/// ### Type Parameters
220		///
221		#[document_type_parameters(
222			"The type of the result of the new task.",
223			"The type of the binding function."
224		)]
225		///
226		/// ### Parameters
227		///
228		#[document_parameters("The function to apply to the result of this task.")]
229		///
230		/// ### Returns
231		///
232		/// A new `Trampoline` that chains `f` after this task.
233		///
234		/// ### Examples
235		///
236		/// ```
237		/// use fp_library::types::*;
238		///
239		/// // This is O(n), not O(n²)
240		/// let mut task = Trampoline::pure(0);
241		/// for i in 0..100 {
242		///     task = task.bind(move |x| Trampoline::pure(x + i));
243		/// }
244		/// ```
245		#[inline]
246		pub fn bind<B: 'static + Send, F>(
247			self,
248			f: F,
249		) -> Trampoline<B>
250		where
251			F: FnOnce(A) -> Trampoline<B> + 'static,
252		{
253			Trampoline(self.0.bind(move |a| f(a).0))
254		}
255
256		/// Functor map: transforms the result without changing structure.
257		///
258		/// ### Type Signature
259		///
260		#[document_signature]
261		///
262		/// ### Type Parameters
263		///
264		#[document_type_parameters(
265			"The type of the result of the mapping function.",
266			"The type of the mapping function."
267		)]
268		///
269		/// ### Parameters
270		///
271		#[document_parameters("The function to apply to the result of this task.")]
272		///
273		/// ### Returns
274		///
275		/// A new `Trampoline` with the transformed result.
276		///
277		/// ### Examples
278		///
279		/// ```
280		/// use fp_library::types::*;
281		///
282		/// let task = Trampoline::pure(10).map(|x| x * 2);
283		/// assert_eq!(task.evaluate(), 20);
284		/// ```
285		#[inline]
286		pub fn map<B: 'static + Send, F>(
287			self,
288			f: F,
289		) -> Trampoline<B>
290		where
291			F: FnOnce(A) -> B + 'static,
292		{
293			self.bind(move |a| Trampoline::pure(f(a)))
294		}
295
296		/// Forces evaluation and returns the result.
297		///
298		/// This runs the trampoline loop, iteratively processing
299		/// the CatList of continuations without growing the stack.
300		///
301		/// ### Type Signature
302		///
303		#[document_signature]
304		///
305		/// ### Parameters
306		///
307		#[document_parameters]
308		///
309		/// ### Returns
310		///
311		/// The result of the computation.
312		///
313		/// ### Examples
314		///
315		/// ```
316		/// use fp_library::types::*;
317		///
318		/// let task = Trampoline::new(|| 1 + 1);
319		/// assert_eq!(task.evaluate(), 2);
320		/// ```
321		pub fn evaluate(self) -> A {
322			self.0.evaluate()
323		}
324
325		/// Combines two `Trampoline`s, running both and combining results.
326		///
327		/// ### Type Signature
328		///
329		#[document_signature]
330		///
331		/// ### Type Parameters
332		///
333		#[document_type_parameters(
334			"The type of the second task's result.",
335			"The type of the combined result.",
336			"The type of the combining function."
337		)]
338		///
339		/// ### Parameters
340		///
341		#[document_parameters("The second task.", "The function to combine the results.")]
342		///
343		/// ### Returns
344		///
345		/// A new `Trampoline` producing the combined result.
346		///
347		/// ### Examples
348		///
349		/// ```
350		/// use fp_library::types::*;
351		///
352		/// let t1 = Trampoline::pure(10);
353		/// let t2 = Trampoline::pure(20);
354		/// let t3 = t1.lift2(t2, |a, b| a + b);
355		/// assert_eq!(t3.evaluate(), 30);
356		/// ```
357		pub fn lift2<B: 'static + Send, C: 'static + Send, F>(
358			self,
359			other: Trampoline<B>,
360			f: F,
361		) -> Trampoline<C>
362		where
363			F: FnOnce(A, B) -> C + 'static,
364		{
365			self.bind(move |a| other.map(move |b| f(a, b)))
366		}
367
368		/// Sequences two `Trampoline`s, discarding the first result.
369		///
370		/// ### Type Signature
371		///
372		#[document_signature]
373		///
374		/// ### Type Parameters
375		///
376		#[document_type_parameters("The type of the second task's result.")]
377		///
378		/// ### Parameters
379		///
380		#[document_parameters("The second task.")]
381		///
382		/// ### Returns
383		///
384		/// A new `Trampoline` that runs both tasks and returns the result of the second.
385		///
386		/// ### Examples
387		///
388		/// ```
389		/// use fp_library::types::*;
390		///
391		/// let t1 = Trampoline::pure(10);
392		/// let t2 = Trampoline::pure(20);
393		/// let t3 = t1.then(t2);
394		/// assert_eq!(t3.evaluate(), 20);
395		/// ```
396		pub fn then<B: 'static + Send>(
397			self,
398			other: Trampoline<B>,
399		) -> Trampoline<B> {
400			self.bind(move |_| other)
401		}
402
403		/// Stack-safe tail recursion within Trampoline.
404		///
405		/// # Clone Bound
406		///
407		/// The function `f` must implement `Clone` because each iteration
408		/// of the recursion may need its own copy. Most closures naturally
409		/// implement `Clone` when all their captures implement `Clone`.
410		///
411		/// For closures that don't implement `Clone`, use `arc_tail_rec_m`
412		/// which wraps the closure in `Arc` internally.
413		///
414		/// ### Type Signature
415		///
416		#[document_signature]
417		///
418		/// ### Type Parameters
419		///
420		#[document_type_parameters("The type of the state.", "The type of the step function.")]
421		///
422		/// ### Parameters
423		///
424		#[document_parameters(
425			"The function that performs one step of the recursion.",
426			"The initial state."
427		)]
428		///
429		/// ### Returns
430		///
431		/// A `Trampoline` that performs the recursion.
432		///
433		/// ### Examples
434		///
435		/// ```
436		/// use fp_library::types::{Trampoline, Step};
437		///
438		/// // Fibonacci using tail recursion
439		/// fn fib(n: u64) -> Trampoline<u64> {
440		///     Trampoline::tail_rec_m(|(n, a, b)| {
441		///         if n == 0 {
442		///             Trampoline::pure(Step::Done(a))
443		///         } else {
444		///             Trampoline::pure(Step::Loop((n - 1, b, a + b)))
445		///         }
446		///     }, (n, 0u64, 1u64))
447		/// }
448		///
449		/// assert_eq!(fib(50).evaluate(), 12586269025);
450		/// ```
451		pub fn tail_rec_m<S: 'static + Send, F>(
452			f: F,
453			initial: S,
454		) -> Self
455		where
456			F: Fn(S) -> Trampoline<Step<S, A>> + Clone + 'static,
457		{
458			// Use defer to ensure each step is trampolined.
459			fn go<A: 'static + Send, B: 'static + Send, F>(
460				f: F,
461				a: A,
462			) -> Trampoline<B>
463			where
464				F: Fn(A) -> Trampoline<Step<A, B>> + Clone + 'static,
465			{
466				let f_clone = f.clone();
467				Trampoline::defer(move || {
468					f(a).bind(move |step| match step {
469						Step::Loop(next) => go(f_clone.clone(), next),
470						Step::Done(b) => Trampoline::pure(b),
471					})
472				})
473			}
474
475			go(f, initial)
476		}
477
478		/// Arc-wrapped version for non-Clone closures.
479		///
480		/// Use this when your closure captures non-Clone state.
481		///
482		/// ### Type Signature
483		///
484		#[document_signature]
485		///
486		/// ### Type Parameters
487		///
488		#[document_type_parameters("The type of the state.", "The type of the step function.")]
489		///
490		/// ### Parameters
491		///
492		#[document_parameters(
493			"The function that performs one step of the recursion.",
494			"The initial state."
495		)]
496		///
497		/// ### Returns
498		///
499		/// A `Trampoline` that performs the recursion.
500		///
501		/// ### Examples
502		///
503		/// ```
504		/// use fp_library::types::{Trampoline, Step};
505		/// use std::sync::{Arc, atomic::{AtomicUsize, Ordering}};
506		///
507		/// // Closure captures non-Clone state
508		/// let counter = Arc::new(AtomicUsize::new(0));
509		/// Trampoline::arc_tail_rec_m(move |n| {
510		///     counter.fetch_add(1, Ordering::SeqCst);
511		///     if n == 0 {
512		///         Trampoline::pure(Step::Done(0))
513		///     } else {
514		///         Trampoline::pure(Step::Loop(n - 1))
515		///     }
516		/// }, 100);
517		/// ```
518		pub fn arc_tail_rec_m<S: 'static + Send, F>(
519			f: F,
520			initial: S,
521		) -> Self
522		where
523			F: Fn(S) -> Trampoline<Step<S, A>> + 'static,
524		{
525			use std::sync::Arc;
526			let f = Arc::new(f);
527			let wrapper = move |s: S| {
528				let f = Arc::clone(&f);
529				f(s)
530			};
531			Self::tail_rec_m(wrapper, initial)
532		}
533	}
534
535	/// ### Type Parameters
536	///
537	#[document_type_parameters(
538		"The type of the value produced by the task.",
539		"The memoization configuration."
540	)]
541	impl<A: 'static + Send + Clone, Config: LazyConfig> From<Lazy<'static, A, Config>>
542		for Trampoline<A>
543	{
544		/// ### Type Signature
545		///
546		#[document_signature]
547		///
548		/// ### Parameters
549		///
550		#[document_parameters("The lazy value to convert.")]
551		fn from(lazy: Lazy<'static, A, Config>) -> Self {
552			Trampoline::new(move || lazy.evaluate().clone())
553		}
554	}
555
556	/// ### Type Parameters
557	///
558	#[document_type_parameters("The type of the value produced by the task.")]
559	impl<A: 'static + Send> Deferrable<'static> for Trampoline<A> {
560		/// Creates a `Trampoline` from a computation that produces it.
561		///
562		/// ### Type Signature
563		///
564		#[document_signature]
565		///
566		/// ### Type Parameters
567		///
568		#[document_type_parameters("The type of the closure.")]
569		///
570		/// ### Parameters
571		///
572		#[document_parameters("A thunk that produces the trampoline.")]
573		///
574		/// ### Returns
575		///
576		/// The deferred trampoline.
577		///
578		/// ### Examples
579		///
580		/// ```
581		/// use fp_library::{brands::*, functions::*, types::*, classes::Deferrable};
582		///
583		/// let task: Trampoline<i32> = Deferrable::defer(|| Trampoline::pure(42));
584		/// assert_eq!(task.evaluate(), 42);
585		/// ```
586		fn defer<F>(f: F) -> Self
587		where
588			F: FnOnce() -> Self + 'static,
589			Self: Sized,
590		{
591			Trampoline::defer(f)
592		}
593	}
594}
595pub use inner::*;
596
597#[cfg(test)]
598mod tests {
599	use super::*;
600	use crate::types::step::Step;
601
602	/// Tests `Trampoline::pure`.
603	///
604	/// Verifies that `pure` creates a task that returns the value immediately.
605	#[test]
606	fn test_task_pure() {
607		let task = Trampoline::pure(42);
608		assert_eq!(task.evaluate(), 42);
609	}
610
611	/// Tests `Trampoline::new`.
612	///
613	/// Verifies that `new` creates a task that computes the value when run.
614	#[test]
615	fn test_task_new() {
616		let task = Trampoline::new(|| 42);
617		assert_eq!(task.evaluate(), 42);
618	}
619
620	/// Tests `Trampoline::bind`.
621	///
622	/// Verifies that `bind` chains computations correctly.
623	#[test]
624	fn test_task_bind() {
625		let task = Trampoline::pure(10).bind(|x| Trampoline::pure(x * 2));
626		assert_eq!(task.evaluate(), 20);
627	}
628
629	/// Tests `Trampoline::map`.
630	///
631	/// Verifies that `map` transforms the result.
632	#[test]
633	fn test_task_map() {
634		let task = Trampoline::pure(10).map(|x| x * 2);
635		assert_eq!(task.evaluate(), 20);
636	}
637
638	/// Tests `Trampoline::defer`.
639	///
640	/// Verifies that `defer` delays the creation of the task.
641	#[test]
642	fn test_task_defer() {
643		let task = Trampoline::defer(|| Trampoline::pure(42));
644		assert_eq!(task.evaluate(), 42);
645	}
646
647	/// Tests `Trampoline::tail_rec_m`.
648	///
649	/// Verifies that `tail_rec_m` performs tail recursion correctly.
650	#[test]
651	fn test_task_tail_rec_m() {
652		fn factorial(n: u64) -> Trampoline<u64> {
653			Trampoline::tail_rec_m(
654				|(n, acc)| {
655					if n <= 1 {
656						Trampoline::pure(Step::Done(acc))
657					} else {
658						Trampoline::pure(Step::Loop((n - 1, n * acc)))
659					}
660				},
661				(n, 1u64),
662			)
663		}
664
665		assert_eq!(factorial(5).evaluate(), 120);
666	}
667
668	/// Tests `Trampoline::map2`.
669	///
670	/// Verifies that `map2` combines two tasks.
671	#[test]
672	fn test_task_map2() {
673		let t1 = Trampoline::pure(10);
674		let t2 = Trampoline::pure(20);
675		let t3 = t1.lift2(t2, |a, b| a + b);
676		assert_eq!(t3.evaluate(), 30);
677	}
678
679	/// Tests `Trampoline::and_then`.
680	///
681	/// Verifies that `and_then` sequences two tasks.
682	#[test]
683	fn test_task_and_then() {
684		let t1 = Trampoline::pure(10);
685		let t2 = Trampoline::pure(20);
686		let t3 = t1.then(t2);
687		assert_eq!(t3.evaluate(), 20);
688	}
689
690	/// Tests `Trampoline::arc_tail_rec_m`.
691	///
692	/// Verifies that `arc_tail_rec_m` works with non-Clone closures.
693	#[test]
694	fn test_task_arc_tail_rec_m() {
695		use std::sync::{
696			Arc,
697			atomic::{AtomicUsize, Ordering},
698		};
699
700		let counter = Arc::new(AtomicUsize::new(0));
701		let counter_clone = Arc::clone(&counter);
702
703		let task = Trampoline::arc_tail_rec_m(
704			move |n| {
705				counter_clone.fetch_add(1, Ordering::SeqCst);
706				if n == 0 {
707					Trampoline::pure(Step::Done(0))
708				} else {
709					Trampoline::pure(Step::Loop(n - 1))
710				}
711			},
712			10,
713		);
714
715		assert_eq!(task.evaluate(), 0);
716		assert_eq!(counter.load(Ordering::SeqCst), 11);
717	}
718
719	/// Tests `Trampoline::from_memo`.
720	///
721	/// Verifies that `From<Lazy>` creates a task that retrieves the memoized value lazily.
722	#[test]
723	fn test_task_from_memo() {
724		use crate::types::{Lazy, RcLazyConfig};
725		use std::cell::RefCell;
726		use std::rc::Rc;
727
728		let counter = Rc::new(RefCell::new(0));
729		let counter_clone = counter.clone();
730		let memo = Lazy::<_, RcLazyConfig>::new(move || {
731			*counter_clone.borrow_mut() += 1;
732			42
733		});
734
735		let task = Trampoline::from(memo.clone());
736
737		// Should not have computed yet (lazy creation)
738		assert_eq!(*counter.borrow(), 0);
739
740		assert_eq!(task.evaluate(), 42);
741		assert_eq!(*counter.borrow(), 1);
742
743		// Run again, should use cached value
744		let task2 = Trampoline::from(memo);
745		assert_eq!(task2.evaluate(), 42);
746		assert_eq!(*counter.borrow(), 1);
747	}
748
749	/// Tests `Trampoline::from` with `ArcLazy`.
750	#[test]
751	fn test_task_from_arc_memo() {
752		use crate::types::{ArcLazyConfig, Lazy};
753		use std::sync::{Arc, Mutex};
754
755		let counter = Arc::new(Mutex::new(0));
756		let counter_clone = counter.clone();
757		let memo = Lazy::<_, ArcLazyConfig>::new(move || {
758			*counter_clone.lock().unwrap() += 1;
759			42
760		});
761
762		let task = Trampoline::from(memo.clone());
763
764		// Should not have computed yet (lazy creation)
765		assert_eq!(*counter.lock().unwrap(), 0);
766
767		assert_eq!(task.evaluate(), 42);
768		assert_eq!(*counter.lock().unwrap(), 1);
769
770		// Run again, should use cached value
771		let task2 = Trampoline::from(memo);
772		assert_eq!(task2.evaluate(), 42);
773		assert_eq!(*counter.lock().unwrap(), 1);
774	}
775}