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