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