fp_library/types/trampoline.rs
1//! Stack-safe computation type with guaranteed safety for unlimited recursion depth.
2//!
3//! Trampolining converts stack-based recursion into heap-based iteration: instead
4//! of each recursive call consuming a stack frame, each step returns a thunk that
5//! the driver loop evaluates iteratively. This eliminates the risk of stack overflow
6//! regardless of recursion depth.
7//!
8//! Built on the [`Free`](crate::types::Free) monad with O(1) [`bind`](Trampoline::bind) operations. Provides complete stack safety at the cost of requiring `'static` types. Use this for deep recursion and heavy monadic pipelines.
9//!
10//! ### Examples
11//!
12//! ```
13//! use fp_library::types::*;
14//!
15//! let task = Trampoline::new(|| 1 + 1)
16//! .bind(|x| Trampoline::new(move || x * 2))
17//! .bind(|x| Trampoline::new(move || x + 10));
18//!
19//! assert_eq!(task.evaluate(), 14);
20//! ```
21
22#[fp_macros::document_module]
23mod inner {
24 use {
25 crate::{
26 brands::ThunkBrand,
27 classes::{
28 Deferrable,
29 LazyConfig,
30 Monoid,
31 Semigroup,
32 },
33 types::{
34 ArcLazyConfig,
35 Free,
36 Lazy,
37 RcLazyConfig,
38 Thunk,
39 },
40 },
41 core::ops::ControlFlow,
42 fp_macros::*,
43 std::fmt,
44 };
45
46 /// A lazy, stack-safe computation that produces a value of type `A`.
47 ///
48 /// `Trampoline` is the "heavy-duty" monadic type for deferred computations that
49 /// require **guaranteed stack safety**. It is built on [`Free<Thunk, A>`] with
50 /// [`CatList`](crate::types::CatList)-based bind stack, ensuring O(1) [`bind`](Trampoline::bind)
51 /// operations and unlimited recursion depth without stack overflow.
52 ///
53 /// # Requirements
54 ///
55 /// - `A: 'static` - Required due to type erasure via [`Box<dyn Any>`].
56 ///
57 /// # Guarantees
58 ///
59 /// - **Stack safe**: Will not overflow regardless of recursion depth.
60 /// - **O(1) bind**: Left-associated `bind` chains don't degrade.
61 /// - **Lazy**: Computation is deferred until [`Trampoline::evaluate`] is called.
62 ///
63 /// # When to Use `Trampoline` vs [`Thunk`]
64 ///
65 /// - Use **`Trampoline<A>`** for deep recursion, heavy monadic pipelines.
66 /// - Use **`Thunk<'a, A>`** for HKT integration, borrowed references, glue code.
67 ///
68 /// # Memoization
69 ///
70 /// `Trampoline` does NOT memoize. Each call to `evaluate` re-evaluates.
71 /// For memoization, wrap in [`Lazy`]:
72 ///
73 /// ```rust
74 /// use fp_library::types::*;
75 ///
76 /// let lazy: Lazy<i32> = Lazy::<_, RcLazyConfig>::new(|| Trampoline::new(|| 1 + 1).evaluate());
77 /// lazy.evaluate(); // Computes
78 /// lazy.evaluate(); // Returns cached
79 /// ```
80 ///
81 /// # Drop behavior
82 ///
83 /// Dropping a `Trampoline` dismantles its inner [`Free<ThunkBrand, A>`](Free)
84 /// chain iteratively. Each suspended thunk in the chain is evaluated during drop
85 /// to access the next node. Be aware that dropping a partially-evaluated
86 /// `Trampoline` may trigger deferred computations.
87 #[document_type_parameters("The type of the value produced by the task.")]
88 ///
89 pub struct Trampoline<A: 'static>(
90 /// The internal `Free` monad representation.
91 Free<ThunkBrand, A>,
92 );
93
94 #[document_type_parameters("The type of the value produced by the task.")]
95 #[document_parameters("The `Trampoline` instance.")]
96 impl<A: 'static> Trampoline<A> {
97 /// Creates a `Trampoline` from an already-computed value.
98 ///
99 /// ### Complexity
100 ///
101 /// O(1) creation, O(1) evaluation
102 #[document_signature]
103 ///
104 #[document_parameters("The value to wrap.")]
105 ///
106 #[document_returns("A `Trampoline` that produces the value `a`.")]
107 ///
108 #[inline]
109 #[document_examples]
110 ///
111 /// ```
112 /// use fp_library::types::*;
113 ///
114 /// let task = Trampoline::pure(42);
115 /// assert_eq!(task.evaluate(), 42);
116 /// ```
117 pub fn pure(a: A) -> Self {
118 Trampoline(Free::pure(a))
119 }
120
121 /// Creates a lazy `Trampoline` that computes `f` on first evaluation.
122 ///
123 /// `Trampoline` does NOT memoize - each `evaluate()`
124 /// re-evaluates. Use [`Lazy`] for caching.
125 ///
126 /// # Complexity
127 /// O(1) creation
128 #[document_signature]
129 ///
130 #[document_parameters("The closure to execute.")]
131 ///
132 #[document_returns("A `Trampoline` that executes `f` when run.")]
133 ///
134 #[inline]
135 #[document_examples]
136 ///
137 /// ```
138 /// use fp_library::types::*;
139 ///
140 /// let task = Trampoline::new(|| {
141 /// // println!("Computing!");
142 /// 1 + 1
143 /// });
144 ///
145 /// // Nothing computed yet
146 /// let result = task.evaluate(); // Now the closure runs
147 /// assert_eq!(result, 2);
148 /// ```
149 pub fn new(f: impl FnOnce() -> A + 'static) -> Self {
150 Trampoline(Free::wrap(Thunk::new(move || Free::pure(f()))))
151 }
152
153 /// Defers the construction of a `Trampoline` itself.
154 ///
155 /// This is critical for stack-safe recursion: instead of
156 /// building a chain of `Trampoline`s directly (which grows the stack),
157 /// we defer the construction.
158 #[document_signature]
159 ///
160 #[document_parameters("The closure that produces a `Trampoline`.")]
161 ///
162 #[document_returns("A `Trampoline` that defers the creation of the inner task.")]
163 ///
164 #[inline]
165 #[document_examples]
166 ///
167 /// ```
168 /// use fp_library::types::*;
169 ///
170 /// fn recursive_sum(
171 /// n: u64,
172 /// acc: u64,
173 /// ) -> Trampoline<u64> {
174 /// if n == 0 {
175 /// Trampoline::pure(acc)
176 /// } else {
177 /// // Defer construction to avoid stack growth
178 /// Trampoline::defer(move || recursive_sum(n - 1, acc + n))
179 /// }
180 /// }
181 ///
182 /// // This works for n = 1_000_000 without stack overflow!
183 /// let result = recursive_sum(1_000, 0).evaluate();
184 /// assert_eq!(result, 500500);
185 /// ```
186 pub fn defer(f: impl FnOnce() -> Trampoline<A> + 'static) -> Self {
187 Trampoline(Free::wrap(Thunk::new(move || f().0)))
188 }
189
190 /// Monadic bind with O(1) complexity.
191 ///
192 /// Chains computations together. The key property is that
193 /// left-associated chains don't degrade to O(n²).
194 #[document_signature]
195 ///
196 #[document_type_parameters("The type of the result of the new task.")]
197 ///
198 #[document_parameters("The function to apply to the result of this task.")]
199 ///
200 #[document_returns("A new `Trampoline` that chains `f` after this task.")]
201 ///
202 #[inline]
203 #[document_examples]
204 ///
205 /// ```
206 /// use fp_library::types::*;
207 ///
208 /// // This is O(n), not O(n²)
209 /// let mut task = Trampoline::pure(0);
210 /// for i in 0 .. 100 {
211 /// task = task.bind(move |x| Trampoline::pure(x + i));
212 /// }
213 /// assert_eq!(task.evaluate(), 4950);
214 /// ```
215 pub fn bind<B: 'static>(
216 self,
217 f: impl FnOnce(A) -> Trampoline<B> + 'static,
218 ) -> Trampoline<B> {
219 Trampoline(self.0.bind(move |a| f(a).0))
220 }
221
222 /// Functor map: transforms the result without changing structure.
223 #[document_signature]
224 ///
225 #[document_type_parameters("The type of the result of the mapping function.")]
226 ///
227 #[document_parameters("The function to apply to the result of this task.")]
228 ///
229 #[document_returns("A new `Trampoline` with the transformed result.")]
230 ///
231 #[inline]
232 #[document_examples]
233 ///
234 /// ```
235 /// use fp_library::types::*;
236 ///
237 /// let task = Trampoline::pure(10).map(|x| x * 2);
238 /// assert_eq!(task.evaluate(), 20);
239 /// ```
240 pub fn map<B: 'static>(
241 self,
242 f: impl FnOnce(A) -> B + 'static,
243 ) -> Trampoline<B> {
244 Trampoline(self.0.map(f))
245 }
246
247 /// Forces evaluation and returns the result.
248 ///
249 /// This runs the trampoline loop, iteratively processing
250 /// the CatList of continuations without growing the stack.
251 #[document_signature]
252 ///
253 #[document_parameters]
254 ///
255 #[document_returns("The result of the computation.")]
256 ///
257 #[document_examples]
258 ///
259 /// ```
260 /// use fp_library::types::*;
261 ///
262 /// let task = Trampoline::new(|| 1 + 1);
263 /// assert_eq!(task.evaluate(), 2);
264 /// ```
265 pub fn evaluate(self) -> A {
266 self.0.evaluate()
267 }
268
269 /// Converts this `Trampoline` into a memoized [`Lazy`](crate::types::Lazy) value.
270 ///
271 /// The computation will be evaluated at most once; subsequent accesses
272 /// return the cached result.
273 #[document_signature]
274 ///
275 #[document_returns(
276 "A memoized `Lazy` value that evaluates this trampoline on first access."
277 )]
278 ///
279 #[document_examples]
280 ///
281 /// ```
282 /// use fp_library::types::*;
283 ///
284 /// let task = Trampoline::new(|| 42);
285 /// let lazy = task.into_rc_lazy();
286 /// // evaluate() returns &i32, so deref to get i32 for comparison
287 /// assert_eq!(*lazy.evaluate(), 42);
288 /// ```
289 pub fn into_rc_lazy(self) -> Lazy<'static, A, RcLazyConfig> {
290 Lazy::from(self)
291 }
292
293 /// Evaluates this `Trampoline` and wraps the result in a thread-safe [`ArcLazy`](crate::types::Lazy).
294 ///
295 /// The trampoline is evaluated eagerly because its inner closures are
296 /// `!Send` (they are stored as `Box<dyn FnOnce>` inside the underlying
297 /// `Free` monad), so they cannot be placed inside an `Arc`-based lazy
298 /// value that requires `Send`. By evaluating first, only the resulting
299 /// `A` (which is `Send + Sync`) needs to cross the thread-safety boundary.
300 #[document_signature]
301 ///
302 #[document_returns("A thread-safe `ArcLazy` containing the eagerly evaluated result.")]
303 ///
304 #[document_examples]
305 ///
306 /// ```
307 /// use fp_library::types::*;
308 ///
309 /// let task = Trampoline::new(|| 42);
310 /// let lazy = task.into_arc_lazy();
311 /// assert_eq!(*lazy.evaluate(), 42);
312 /// ```
313 pub fn into_arc_lazy(self) -> Lazy<'static, A, ArcLazyConfig>
314 where
315 A: Send + Sync, {
316 Lazy::from(self)
317 }
318
319 /// Combines two `Trampoline`s, running both and combining results.
320 #[document_signature]
321 ///
322 #[document_type_parameters(
323 "The type of the second task's result.",
324 "The type of the combined result."
325 )]
326 ///
327 #[document_parameters("The second task.", "The function to combine the results.")]
328 ///
329 #[document_returns("A new `Trampoline` producing the combined result.")]
330 ///
331 #[document_examples]
332 ///
333 /// ```
334 /// use fp_library::types::*;
335 ///
336 /// let t1 = Trampoline::pure(10);
337 /// let t2 = Trampoline::pure(20);
338 /// let t3 = t1.lift2(t2, |a, b| a + b);
339 /// assert_eq!(t3.evaluate(), 30);
340 /// ```
341 pub fn lift2<B: 'static, C: 'static>(
342 self,
343 other: Trampoline<B>,
344 f: impl FnOnce(A, B) -> C + 'static,
345 ) -> Trampoline<C> {
346 self.bind(move |a| other.map(move |b| f(a, b)))
347 }
348
349 /// Sequences two `Trampoline`s, discarding the first result.
350 #[document_signature]
351 ///
352 #[document_type_parameters("The type of the second task's result.")]
353 ///
354 #[document_parameters("The second task.")]
355 ///
356 #[document_returns(
357 "A new `Trampoline` that runs both tasks and returns the result of the second."
358 )]
359 ///
360 #[document_examples]
361 ///
362 /// ```
363 /// use fp_library::types::*;
364 ///
365 /// let t1 = Trampoline::pure(10);
366 /// let t2 = Trampoline::pure(20);
367 /// let t3 = t1.then(t2);
368 /// assert_eq!(t3.evaluate(), 20);
369 /// ```
370 pub fn then<B: 'static>(
371 self,
372 other: Trampoline<B>,
373 ) -> Trampoline<B> {
374 self.bind(move |_| other)
375 }
376
377 /// Combines two `Trampoline` values using the `Semigroup` operation on their results.
378 ///
379 /// Evaluates both trampolines and combines the results via [`Semigroup::append`].
380 /// The combination itself is deferred and stack-safe.
381 #[document_signature]
382 ///
383 #[document_parameters(
384 "The second `Trampoline` whose result will be combined with this one."
385 )]
386 ///
387 #[document_returns("A new `Trampoline` producing the combined result.")]
388 ///
389 #[document_examples]
390 ///
391 /// ```
392 /// use fp_library::types::*;
393 ///
394 /// let t1 = Trampoline::pure(vec![1, 2]);
395 /// let t2 = Trampoline::pure(vec![3, 4]);
396 /// assert_eq!(t1.append(t2).evaluate(), vec![1, 2, 3, 4]);
397 /// ```
398 #[inline]
399 pub fn append(
400 self,
401 other: Trampoline<A>,
402 ) -> Trampoline<A>
403 where
404 A: Semigroup + 'static, {
405 self.lift2(other, Semigroup::append)
406 }
407
408 /// Creates a `Trampoline` that produces the identity element for the given `Monoid`.
409 #[document_signature]
410 ///
411 #[document_returns("A `Trampoline` producing the monoid identity element.")]
412 ///
413 #[document_examples]
414 ///
415 /// ```
416 /// use fp_library::types::*;
417 ///
418 /// let t: Trampoline<Vec<i32>> = Trampoline::empty();
419 /// assert_eq!(t.evaluate(), Vec::<i32>::new());
420 /// ```
421 #[inline]
422 pub fn empty() -> Trampoline<A>
423 where
424 A: Monoid + 'static, {
425 Trampoline::pure(Monoid::empty())
426 }
427
428 /// Stack-safe tail recursion within Trampoline.
429 ///
430 /// # Clone Bound
431 ///
432 /// The function `f` must implement `Clone` because each iteration
433 /// of the recursion may need its own copy. Most closures naturally
434 /// implement `Clone` when all their captures implement `Clone`.
435 ///
436 /// For closures that don't implement `Clone`, use `arc_tail_rec_m`
437 /// which wraps the closure in `Arc` internally.
438 #[document_signature]
439 ///
440 #[document_type_parameters("The type of the state.")]
441 ///
442 #[document_parameters(
443 "The function that performs one step of the recursion.",
444 "The initial state."
445 )]
446 ///
447 #[document_returns("A `Trampoline` that performs the recursion.")]
448 #[document_examples]
449 ///
450 /// ```
451 /// use {
452 /// core::ops::ControlFlow,
453 /// fp_library::types::Trampoline,
454 /// };
455 ///
456 /// // Fibonacci using tail recursion
457 /// fn fib(n: u64) -> Trampoline<u64> {
458 /// Trampoline::tail_rec_m(
459 /// |(n, a, b)| {
460 /// if n == 0 {
461 /// Trampoline::pure(ControlFlow::Break(a))
462 /// } else {
463 /// Trampoline::pure(ControlFlow::Continue((n - 1, b, a + b)))
464 /// }
465 /// },
466 /// (n, 0u64, 1u64),
467 /// )
468 /// }
469 ///
470 /// assert_eq!(fib(50).evaluate(), 12586269025);
471 /// ```
472 pub fn tail_rec_m<S: 'static>(
473 f: impl Fn(S) -> Trampoline<ControlFlow<A, S>> + Clone + 'static,
474 initial: S,
475 ) -> Self {
476 // Use defer to ensure each step is trampolined.
477 fn go<A: 'static, B: 'static, F>(
478 f: F,
479 a: A,
480 ) -> Trampoline<B>
481 where
482 F: Fn(A) -> Trampoline<ControlFlow<B, A>> + Clone + 'static, {
483 Trampoline::defer(move || {
484 let result = f(a);
485 result.bind(move |step| match step {
486 ControlFlow::Continue(next) => go(f, next),
487 ControlFlow::Break(b) => Trampoline::pure(b),
488 })
489 })
490 }
491
492 go(f, initial)
493 }
494
495 /// Arc-wrapped version for non-Clone closures.
496 ///
497 /// Use this when your closure captures non-Clone state.
498 #[document_signature]
499 ///
500 #[document_type_parameters("The type of the state.")]
501 ///
502 #[document_parameters(
503 "The function that performs one step of the recursion.",
504 "The initial state."
505 )]
506 ///
507 #[document_returns("A `Trampoline` that performs the recursion.")]
508 #[document_examples]
509 ///
510 /// ```
511 /// use {
512 /// core::ops::ControlFlow,
513 /// fp_library::types::Trampoline,
514 /// std::sync::{
515 /// Arc,
516 /// atomic::{
517 /// AtomicUsize,
518 /// Ordering,
519 /// },
520 /// },
521 /// };
522 ///
523 /// // Closure captures non-Clone state
524 /// let counter = Arc::new(AtomicUsize::new(0));
525 /// let counter_clone = Arc::clone(&counter);
526 /// let task = Trampoline::arc_tail_rec_m(
527 /// move |n| {
528 /// counter_clone.fetch_add(1, Ordering::SeqCst);
529 /// if n == 0 {
530 /// Trampoline::pure(ControlFlow::Break(0))
531 /// } else {
532 /// Trampoline::pure(ControlFlow::Continue(n - 1))
533 /// }
534 /// },
535 /// 100,
536 /// );
537 /// assert_eq!(task.evaluate(), 0);
538 /// assert_eq!(counter.load(Ordering::SeqCst), 101);
539 /// ```
540 pub fn arc_tail_rec_m<S: 'static>(
541 f: impl Fn(S) -> Trampoline<ControlFlow<A, S>> + 'static,
542 initial: S,
543 ) -> Self {
544 use std::sync::Arc;
545 let f = Arc::new(f);
546 let wrapper = move |s: S| {
547 let f = Arc::clone(&f);
548 f(s)
549 };
550 Self::tail_rec_m(wrapper, initial)
551 }
552
553 /// Peels off one layer of the trampoline.
554 ///
555 /// Returns `Ok(a)` if the computation has already completed with value `a`,
556 /// or `Err(thunk)` if the computation is suspended. Evaluating the returned
557 /// [`Thunk`] yields the next `Trampoline` step.
558 ///
559 /// This is useful for implementing custom interpreters or drivers that need
560 /// to interleave trampoline steps with other logic (e.g., logging, resource
561 /// cleanup, cooperative scheduling).
562 #[document_signature]
563 ///
564 #[document_returns(
565 "`Ok(a)` if the computation is finished, `Err(thunk)` if it is suspended."
566 )]
567 ///
568 #[document_examples]
569 ///
570 /// ```
571 /// use fp_library::types::*;
572 ///
573 /// // A pure trampoline resumes immediately.
574 /// let t = Trampoline::pure(42);
575 /// assert_eq!(t.resume().unwrap(), 42);
576 ///
577 /// // A deferred trampoline is suspended.
578 /// let t = Trampoline::defer(|| Trampoline::pure(99));
579 /// match t.resume() {
580 /// Ok(_) => panic!("expected suspension"),
581 /// Err(thunk) => {
582 /// let next = thunk.evaluate();
583 /// assert_eq!(next.resume().unwrap(), 99);
584 /// }
585 /// }
586 /// ```
587 pub fn resume(self) -> Result<A, Thunk<'static, Trampoline<A>>> {
588 match self.0.resume() {
589 Ok(a) => Ok(a),
590 Err(thunk_of_free) => Err(thunk_of_free.map(Trampoline)),
591 }
592 }
593 }
594
595 #[document_type_parameters(
596 "The type of the value produced by the task.",
597 "The memoization configuration."
598 )]
599 impl<A: 'static + Clone, Config: LazyConfig> From<Lazy<'static, A, Config>> for Trampoline<A> {
600 /// Converts a [`Lazy`] value into a [`Trampoline`] by cloning the memoized value.
601 ///
602 /// This conversion clones the cached value on each evaluation.
603 /// The cost depends on the [`Clone`] implementation of `A`.
604 #[document_signature]
605 #[document_parameters("The lazy value to convert.")]
606 #[document_returns("A trampoline that evaluates the lazy value.")]
607 #[document_examples]
608 ///
609 /// ```
610 /// use fp_library::types::*;
611 /// let lazy = Lazy::<_, RcLazyConfig>::pure(42);
612 /// let task = Trampoline::from(lazy);
613 /// assert_eq!(task.evaluate(), 42);
614 /// ```
615 fn from(lazy: Lazy<'static, A, Config>) -> Self {
616 Trampoline::new(move || lazy.evaluate().clone())
617 }
618 }
619
620 #[document_type_parameters("The type of the value produced by the task.")]
621 impl<A: 'static> Deferrable<'static> for Trampoline<A> {
622 /// Creates a `Trampoline` from a computation that produces it.
623 #[document_signature]
624 ///
625 #[document_parameters("A thunk that produces the trampoline.")]
626 ///
627 #[document_returns("The deferred trampoline.")]
628 ///
629 #[document_examples]
630 ///
631 /// ```
632 /// use fp_library::{
633 /// brands::*,
634 /// classes::Deferrable,
635 /// functions::*,
636 /// types::*,
637 /// };
638 ///
639 /// let task: Trampoline<i32> = Deferrable::defer(|| Trampoline::pure(42));
640 /// assert_eq!(task.evaluate(), 42);
641 /// ```
642 fn defer(f: impl FnOnce() -> Self + 'static) -> Self
643 where
644 Self: Sized, {
645 Trampoline::defer(f)
646 }
647 }
648
649 #[document_type_parameters("The type of the value produced by the task.")]
650 impl<A: Semigroup + 'static> Semigroup for Trampoline<A> {
651 /// Combines two `Trampoline`s by combining their results via [`Semigroup::append`].
652 #[document_signature]
653 ///
654 #[document_parameters("The first `Trampoline`.", "The second `Trampoline`.")]
655 ///
656 #[document_returns("A new `Trampoline` producing the combined result.")]
657 ///
658 #[document_examples]
659 ///
660 /// ```
661 /// use fp_library::{
662 /// classes::*,
663 /// functions::*,
664 /// types::*,
665 /// };
666 ///
667 /// let t1 = Trampoline::pure(vec![1, 2]);
668 /// let t2 = Trampoline::pure(vec![3, 4]);
669 /// let t3 = append::<_>(t1, t2);
670 /// assert_eq!(t3.evaluate(), vec![1, 2, 3, 4]);
671 /// ```
672 fn append(
673 a: Self,
674 b: Self,
675 ) -> Self {
676 a.lift2(b, Semigroup::append)
677 }
678 }
679
680 #[document_type_parameters("The type of the value produced by the task.")]
681 impl<A: Monoid + 'static> Monoid for Trampoline<A> {
682 /// Returns a `Trampoline` producing the identity element for `A`.
683 #[document_signature]
684 ///
685 #[document_returns("A `Trampoline` producing the monoid identity element.")]
686 ///
687 #[document_examples]
688 ///
689 /// ```
690 /// use fp_library::{
691 /// classes::*,
692 /// functions::*,
693 /// types::*,
694 /// };
695 ///
696 /// let t: Trampoline<Vec<i32>> = empty::<Trampoline<Vec<i32>>>();
697 /// assert_eq!(t.evaluate(), Vec::<i32>::new());
698 /// ```
699 fn empty() -> Self {
700 Trampoline::pure(Monoid::empty())
701 }
702 }
703
704 #[document_type_parameters("The type of the value produced by the task.")]
705 #[document_parameters("The trampoline to format.")]
706 impl<A: 'static> fmt::Debug for Trampoline<A> {
707 /// Formats the trampoline without evaluating it.
708 #[document_signature]
709 #[document_parameters("The formatter.")]
710 #[document_returns("The formatting result.")]
711 #[document_examples]
712 ///
713 /// ```
714 /// use fp_library::types::*;
715 /// let task = Trampoline::pure(42);
716 /// assert_eq!(format!("{:?}", task), "Trampoline(<unevaluated>)");
717 /// ```
718 fn fmt(
719 &self,
720 f: &mut fmt::Formatter<'_>,
721 ) -> fmt::Result {
722 f.write_str("Trampoline(<unevaluated>)")
723 }
724 }
725}
726pub use inner::*;
727
728#[cfg(test)]
729#[expect(
730 clippy::unwrap_used,
731 clippy::panic,
732 reason = "Tests use panicking operations for brevity and clarity"
733)]
734mod tests {
735 use {
736 super::*,
737 core::ops::ControlFlow,
738 quickcheck_macros::quickcheck,
739 };
740
741 /// Tests `Trampoline::pure`.
742 ///
743 /// Verifies that `pure` creates a task that returns the value immediately.
744 #[test]
745 fn test_task_pure() {
746 let task = Trampoline::pure(42);
747 assert_eq!(task.evaluate(), 42);
748 }
749
750 /// Tests `Trampoline::new`.
751 ///
752 /// Verifies that `new` creates a task that computes the value when run.
753 #[test]
754 fn test_task_new() {
755 let task = Trampoline::new(|| 42);
756 assert_eq!(task.evaluate(), 42);
757 }
758
759 /// Tests `Trampoline::bind`.
760 ///
761 /// Verifies that `bind` chains computations correctly.
762 #[test]
763 fn test_task_bind() {
764 let task = Trampoline::pure(10).bind(|x| Trampoline::pure(x * 2));
765 assert_eq!(task.evaluate(), 20);
766 }
767
768 /// Tests `Trampoline::map`.
769 ///
770 /// Verifies that `map` transforms the result.
771 #[test]
772 fn test_task_map() {
773 let task = Trampoline::pure(10).map(|x| x * 2);
774 assert_eq!(task.evaluate(), 20);
775 }
776
777 /// Tests `Trampoline::defer`.
778 ///
779 /// Verifies that `defer` delays the creation of the task.
780 #[test]
781 fn test_task_defer() {
782 let task = Trampoline::defer(|| Trampoline::pure(42));
783 assert_eq!(task.evaluate(), 42);
784 }
785
786 /// Tests `Trampoline::tail_rec_m`.
787 ///
788 /// Verifies that `tail_rec_m` performs tail recursion correctly.
789 #[test]
790 fn test_task_tail_rec_m() {
791 fn factorial(n: u64) -> Trampoline<u64> {
792 Trampoline::tail_rec_m(
793 |(n, acc)| {
794 if n <= 1 {
795 Trampoline::pure(ControlFlow::Break(acc))
796 } else {
797 Trampoline::pure(ControlFlow::Continue((n - 1, n * acc)))
798 }
799 },
800 (n, 1u64),
801 )
802 }
803
804 assert_eq!(factorial(5).evaluate(), 120);
805 }
806
807 /// Tests `Trampoline::lift2`.
808 ///
809 /// Verifies that `lift2` combines two tasks.
810 #[test]
811 fn test_task_lift2() {
812 let t1 = Trampoline::pure(10);
813 let t2 = Trampoline::pure(20);
814 let t3 = t1.lift2(t2, |a, b| a + b);
815 assert_eq!(t3.evaluate(), 30);
816 }
817
818 /// Tests `Trampoline::then`.
819 ///
820 /// Verifies that `then` sequences two tasks.
821 #[test]
822 fn test_task_then() {
823 let t1 = Trampoline::pure(10);
824 let t2 = Trampoline::pure(20);
825 let t3 = t1.then(t2);
826 assert_eq!(t3.evaluate(), 20);
827 }
828
829 /// Tests `Trampoline::arc_tail_rec_m`.
830 ///
831 /// Verifies that `arc_tail_rec_m` works with non-Clone closures.
832 #[test]
833 fn test_task_arc_tail_rec_m() {
834 use std::sync::{
835 Arc,
836 atomic::{
837 AtomicUsize,
838 Ordering,
839 },
840 };
841
842 let counter = Arc::new(AtomicUsize::new(0));
843 let counter_clone = Arc::clone(&counter);
844
845 let task = Trampoline::arc_tail_rec_m(
846 move |n| {
847 counter_clone.fetch_add(1, Ordering::SeqCst);
848 if n == 0 {
849 Trampoline::pure(ControlFlow::Break(0))
850 } else {
851 Trampoline::pure(ControlFlow::Continue(n - 1))
852 }
853 },
854 10,
855 );
856
857 assert_eq!(task.evaluate(), 0);
858 assert_eq!(counter.load(Ordering::SeqCst), 11);
859 }
860
861 /// Tests `Trampoline::from_memo`.
862 ///
863 /// Verifies that `From<Lazy>` creates a task that retrieves the memoized value lazily.
864 #[test]
865 fn test_task_from_memo() {
866 use {
867 crate::types::{
868 Lazy,
869 RcLazyConfig,
870 },
871 std::{
872 cell::RefCell,
873 rc::Rc,
874 },
875 };
876
877 let counter = Rc::new(RefCell::new(0));
878 let counter_clone = counter.clone();
879 let memo = Lazy::<_, RcLazyConfig>::new(move || {
880 *counter_clone.borrow_mut() += 1;
881 42
882 });
883
884 let task = Trampoline::from(memo.clone());
885
886 // Should not have computed yet (lazy creation)
887 assert_eq!(*counter.borrow(), 0);
888
889 assert_eq!(task.evaluate(), 42);
890 assert_eq!(*counter.borrow(), 1);
891
892 // Run again, should use cached value
893 let task2 = Trampoline::from(memo);
894 assert_eq!(task2.evaluate(), 42);
895 assert_eq!(*counter.borrow(), 1);
896 }
897
898 /// Tests `Trampoline::from` with `ArcLazy`.
899 #[test]
900 fn test_task_from_arc_memo() {
901 use {
902 crate::types::{
903 ArcLazyConfig,
904 Lazy,
905 },
906 std::sync::{
907 Arc,
908 Mutex,
909 },
910 };
911
912 let counter = Arc::new(Mutex::new(0));
913 let counter_clone = counter.clone();
914 let memo = Lazy::<_, ArcLazyConfig>::new(move || {
915 *counter_clone.lock().unwrap() += 1;
916 42
917 });
918
919 let task = Trampoline::from(memo.clone());
920
921 // Should not have computed yet (lazy creation)
922 assert_eq!(*counter.lock().unwrap(), 0);
923
924 assert_eq!(task.evaluate(), 42);
925 assert_eq!(*counter.lock().unwrap(), 1);
926
927 // Run again, should use cached value
928 let task2 = Trampoline::from(memo);
929 assert_eq!(task2.evaluate(), 42);
930 assert_eq!(*counter.lock().unwrap(), 1);
931 }
932
933 /// Tests `From<Thunk>` for `Trampoline`.
934 ///
935 /// Verifies that converting a `Thunk` to a `Trampoline` preserves the computed value.
936 #[test]
937 fn test_task_from_thunk() {
938 use crate::types::Thunk;
939
940 let thunk = Thunk::pure(42);
941 let task = Trampoline::from(thunk);
942 assert_eq!(task.evaluate(), 42);
943 }
944
945 /// Tests roundtrip `Thunk` -> `Trampoline` -> evaluate.
946 ///
947 /// Verifies that a lazy thunk is correctly evaluated when converted to a trampoline.
948 #[test]
949 fn test_task_from_thunk_lazy() {
950 use crate::types::Thunk;
951
952 let thunk = Thunk::new(|| 21 * 2);
953 let task = Trampoline::from(thunk);
954 assert_eq!(task.evaluate(), 42);
955 }
956
957 // QuickCheck Law Tests
958
959 // Functor Laws (via inherent methods)
960
961 /// Functor identity: `pure(a).map(identity) == a`.
962 #[quickcheck]
963 fn functor_identity(x: i32) -> bool {
964 Trampoline::pure(x).map(|a| a).evaluate() == x
965 }
966
967 /// Functor composition: `fa.map(f . g) == fa.map(g).map(f)`.
968 #[quickcheck]
969 fn functor_composition(x: i32) -> bool {
970 let f = |a: i32| a.wrapping_add(1);
971 let g = |a: i32| a.wrapping_mul(2);
972 let lhs = Trampoline::pure(x).map(move |a| f(g(a))).evaluate();
973 let rhs = Trampoline::pure(x).map(g).map(f).evaluate();
974 lhs == rhs
975 }
976
977 // Monad Laws (via inherent methods)
978
979 /// Monad left identity: `pure(a).bind(f) == f(a)`.
980 #[quickcheck]
981 fn monad_left_identity(a: i32) -> bool {
982 let f = |x: i32| Trampoline::pure(x.wrapping_mul(2));
983 Trampoline::pure(a).bind(f).evaluate() == f(a).evaluate()
984 }
985
986 /// Monad right identity: `m.bind(pure) == m`.
987 #[quickcheck]
988 fn monad_right_identity(x: i32) -> bool {
989 Trampoline::pure(x).bind(Trampoline::pure).evaluate() == x
990 }
991
992 /// Monad associativity: `m.bind(f).bind(g) == m.bind(|a| f(a).bind(g))`.
993 #[quickcheck]
994 fn monad_associativity(x: i32) -> bool {
995 let f = |a: i32| Trampoline::pure(a.wrapping_add(1));
996 let g = |a: i32| Trampoline::pure(a.wrapping_mul(3));
997 let lhs = Trampoline::pure(x).bind(f).bind(g).evaluate();
998 let rhs = Trampoline::pure(x).bind(move |a| f(a).bind(g)).evaluate();
999 lhs == rhs
1000 }
1001
1002 // Tests for !Send types (Rc)
1003
1004 /// Tests that `Trampoline` works with `Rc<T>`, a `!Send` type.
1005 ///
1006 /// This verifies that the `Send` bound relaxation allows single-threaded
1007 /// stack-safe recursion with reference-counted types.
1008 #[test]
1009 fn test_trampoline_with_rc() {
1010 use std::rc::Rc;
1011
1012 let rc_val = Rc::new(42);
1013 let task = Trampoline::pure(rc_val);
1014 let result = task.evaluate();
1015 assert_eq!(*result, 42);
1016 }
1017
1018 /// Tests `Trampoline::bind` with `Rc<T>`.
1019 ///
1020 /// Verifies that `bind` works correctly when the value type is `!Send`.
1021 #[test]
1022 fn test_trampoline_bind_with_rc() {
1023 use std::rc::Rc;
1024
1025 let task = Trampoline::pure(Rc::new(10)).bind(|rc| {
1026 let val = *rc;
1027 Trampoline::pure(Rc::new(val * 2))
1028 });
1029 assert_eq!(*task.evaluate(), 20);
1030 }
1031
1032 /// Tests `Trampoline::map` with `Rc<T>`.
1033 ///
1034 /// Verifies that `map` works correctly with `!Send` types.
1035 #[test]
1036 fn test_trampoline_map_with_rc() {
1037 use std::rc::Rc;
1038
1039 let task = Trampoline::pure(Rc::new(10)).map(|rc| Rc::new(*rc * 3));
1040 assert_eq!(*task.evaluate(), 30);
1041 }
1042
1043 /// Tests `Trampoline::defer` with `Rc<T>`.
1044 ///
1045 /// Verifies that deferred construction works with `!Send` types.
1046 #[test]
1047 fn test_trampoline_defer_with_rc() {
1048 use std::rc::Rc;
1049
1050 let task = Trampoline::defer(|| Trampoline::pure(Rc::new(42)));
1051 assert_eq!(*task.evaluate(), 42);
1052 }
1053
1054 /// Tests `Trampoline::tail_rec_m` with `Rc<T>`.
1055 ///
1056 /// Verifies that stack-safe recursion works with `!Send` types.
1057 #[test]
1058 fn test_trampoline_tail_rec_m_with_rc() {
1059 use std::rc::Rc;
1060
1061 let task = Trampoline::tail_rec_m(
1062 |(n, acc): (u64, Rc<u64>)| {
1063 if n == 0 {
1064 Trampoline::pure(ControlFlow::Break(acc))
1065 } else {
1066 Trampoline::pure(ControlFlow::Continue((n - 1, Rc::new(*acc + n))))
1067 }
1068 },
1069 (100u64, Rc::new(0u64)),
1070 );
1071 assert_eq!(*task.evaluate(), 5050);
1072 }
1073
1074 #[test]
1075 fn test_trampoline_append() {
1076 let t1 = Trampoline::pure(vec![1, 2]);
1077 let t2 = Trampoline::pure(vec![3, 4]);
1078 assert_eq!(t1.append(t2).evaluate(), vec![1, 2, 3, 4]);
1079 }
1080
1081 #[test]
1082 fn test_trampoline_append_strings() {
1083 let t1 = Trampoline::pure("hello".to_string());
1084 let t2 = Trampoline::pure(" world".to_string());
1085 assert_eq!(t1.append(t2).evaluate(), "hello world");
1086 }
1087
1088 #[test]
1089 fn test_trampoline_empty() {
1090 let t: Trampoline<Vec<i32>> = Trampoline::empty();
1091 assert_eq!(t.evaluate(), Vec::<i32>::new());
1092 }
1093
1094 #[test]
1095 fn test_trampoline_append_with_empty() {
1096 let t1 = Trampoline::pure(vec![1, 2, 3]);
1097 let t2: Trampoline<Vec<i32>> = Trampoline::empty();
1098 assert_eq!(t1.append(t2).evaluate(), vec![1, 2, 3]);
1099 }
1100
1101 // 7.7: Deeper stack safety stress test for tail_rec_m
1102
1103 /// Stress test for `Trampoline::tail_rec_m` with 100,000+ iterations.
1104 ///
1105 /// Verifies that stack safety holds at depths far exceeding typical stack limits.
1106 #[test]
1107 fn test_tail_rec_m_deep_stack_safety() {
1108 let n: u64 = 200_000;
1109 let result = Trampoline::tail_rec_m(
1110 move |acc: u64| {
1111 if acc >= n {
1112 Trampoline::pure(ControlFlow::Break(acc))
1113 } else {
1114 Trampoline::pure(ControlFlow::Continue(acc + 1))
1115 }
1116 },
1117 0u64,
1118 );
1119 assert_eq!(result.evaluate(), n);
1120 }
1121
1122 /// Stress test for `Trampoline::arc_tail_rec_m` with 100,000+ iterations.
1123 ///
1124 /// Verifies that the `Arc`-based variant is also stack-safe at high depth.
1125 #[test]
1126 fn test_arc_tail_rec_m_deep_stack_safety() {
1127 let n: u64 = 200_000;
1128 let result = Trampoline::arc_tail_rec_m(
1129 move |acc: u64| {
1130 if acc >= n {
1131 Trampoline::pure(ControlFlow::Break(acc))
1132 } else {
1133 Trampoline::pure(ControlFlow::Continue(acc + 1))
1134 }
1135 },
1136 0u64,
1137 );
1138 assert_eq!(result.evaluate(), n);
1139 }
1140
1141 /// Tests `Trampoline::resume` on a pure value.
1142 ///
1143 /// Verifies that resuming a pure trampoline returns `Ok(value)`.
1144 #[test]
1145 fn test_resume_pure() {
1146 let t = Trampoline::pure(42);
1147 assert_eq!(t.resume().unwrap(), 42);
1148 }
1149
1150 /// Tests `Trampoline::resume` on a deferred computation.
1151 ///
1152 /// Verifies that resuming a deferred trampoline returns `Err(thunk)`,
1153 /// and evaluating the thunk yields another trampoline that can be resumed.
1154 #[test]
1155 fn test_resume_deferred() {
1156 let t = Trampoline::defer(|| Trampoline::pure(99));
1157 match t.resume() {
1158 Ok(_) => panic!("expected suspension"),
1159 Err(thunk) => {
1160 let next = thunk.evaluate();
1161 assert_eq!(next.resume().unwrap(), 99);
1162 }
1163 }
1164 }
1165
1166 /// Tests that resuming through a chain of deferred steps eventually reaches `Ok`.
1167 ///
1168 /// Builds a chain of three deferred steps and manually drives it to completion.
1169 #[test]
1170 fn test_resume_chain_reaches_ok() {
1171 let t =
1172 Trampoline::defer(|| Trampoline::defer(|| Trampoline::defer(|| Trampoline::pure(7))));
1173
1174 let mut current = t;
1175 let mut steps = 0;
1176 loop {
1177 match current.resume() {
1178 Ok(value) => {
1179 assert_eq!(value, 7);
1180 break;
1181 }
1182 Err(thunk) => {
1183 current = thunk.evaluate();
1184 steps += 1;
1185 }
1186 }
1187 }
1188 assert!(steps > 0, "expected at least one suspension step");
1189 }
1190}