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