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}