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}