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