fp_library/types/free.rs
1//! Stack-safe Free monad over a functor with O(1) [`bind`](crate::functions::bind) operations.
2//!
3//! **User story:** "I want to chain binds without blowing the stack." Useful
4//! for deep recursion and embedded DSLs.
5//!
6//! Enables building computation chains without stack overflow by using a catenable list of continuations. Note: requires `'static` types and cannot implement the library's HKT traits due to type erasure.
7//!
8//! ## Comparison with PureScript
9//!
10//! This implementation is based on the PureScript [`Control.Monad.Free`](https://github.com/purescript/purescript-free/blob/master/src/Control/Monad/Free.purs) module
11//! and the ["Reflection without Remorse"](http://okmij.org/ftp/Haskell/zseq.pdf) technique. It shares the same core algorithmic properties (O(1) bind, stack safety)
12//! but differs significantly in its intended use case and API surface.
13//!
14//! ### Key Differences
15//!
16//! 1. **Interpretation Strategy**:
17//! * **PureScript**: Designed as a generic Abstract Syntax Tree (AST) that can be interpreted into *any* target
18//! monad using `runFree` or `foldFree` by providing a natural transformation at runtime.
19//! * **Rust**: Designed primarily for **stack-safe execution** of computations. The interpretation logic is
20//! baked into the [`Extract`](crate::classes::Extract) trait implemented by the functor `F`.
21//! The [`Free::wrap`] method wraps a functor layer containing a Free computation.
22//!
23//! 2. **API Surface**:
24//! * **PureScript**: Rich API including `liftF`, `hoistFree`, `resume`, `foldFree`.
25//! * **Rust**: Focused API with construction (`pure`, `wrap`, `lift_f`, `bind`), execution (`evaluate`),
26//! introspection (`resume`), and interpretation (`fold_free`).
27//! * `hoistFree` is provided as [`hoist_free`](Free::hoist_free).
28//!
29//! 3. **Terminology**:
30//! * Rust's `Free::wrap` corresponds to PureScript's `wrap`.
31//!
32//! ### Capabilities and Limitations
33//!
34//! **What it CAN do:**
35//! * Provide stack-safe recursion for monadic computations (trampolining).
36//! * Prevent stack overflows when chaining many `bind` operations.
37//! * Execute self-describing effects (like [`Thunk`](crate::types::Thunk)).
38//!
39//! **What it CANNOT do (easily):**
40//! * Act as a generic DSL where the interpretation is decoupled from the operation type.
41//! * *Example*: You cannot easily define a `DatabaseOp` enum and interpret it differently for
42//! production (SQL) and testing (InMemory) using this `Free` implementation, because
43//! `DatabaseOp` must implement a single `Extract` trait.
44//! * Note: `fold_free` with `NaturalTransformation` does support this pattern for simple cases.
45//!
46//! ### Lifetimes and Memory Management
47//!
48//! * **PureScript**: Relies on a garbage collector and `unsafeCoerce`. This allows it to ignore
49//! lifetimes and ownership, enabling a simpler implementation that supports all types.
50//! * **Rust**: Relies on ownership and `Box<dyn Any>` for type erasure. `Any` requires `'static`
51//! to ensure memory safety (preventing use-after-free of references). This forces `Free` to
52//! only work with `'static` types, preventing it from implementing the library's HKT traits
53//! which require lifetime polymorphism.
54//!
55//! ### Examples
56//!
57//! ```
58//! use fp_library::{
59//! brands::*,
60//! types::*,
61//! };
62//!
63//! // Stack-safe recursion
64//! let free = Free::<ThunkBrand, _>::pure(42).bind(|x| Free::pure(x + 1));
65//! ```
66
67#[fp_macros::document_module]
68mod inner {
69 use {
70 crate::{
71 Apply,
72 brands::ThunkBrand,
73 classes::{
74 Deferrable,
75 Extract,
76 Functor,
77 MonadRec,
78 NaturalTransformation,
79 },
80 kinds::*,
81 types::{
82 CatList,
83 Thunk,
84 },
85 },
86 core::ops::ControlFlow,
87 fp_macros::*,
88 std::{
89 any::Any,
90 marker::PhantomData,
91 },
92 };
93
94 /// A type-erased value for internal use.
95 ///
96 /// This type alias represents a value whose type has been erased to [`Box<dyn Any>`].
97 /// It is used within the internal implementation of [`Free`] to allow for
98 /// heterogeneous chains of computations in the [`CatList`].
99 pub type TypeErasedValue = Box<dyn Any>;
100
101 /// A type-erased continuation.
102 ///
103 /// This type alias represents a function that takes a [`TypeErasedValue`]
104 /// and returns a new [`Free`] computation (also type-erased).
105 #[document_type_parameters("The base functor.")]
106 pub type Continuation<F> = Box<dyn FnOnce(TypeErasedValue) -> Free<F, TypeErasedValue>>;
107
108 /// The internal view of the [`Free`] monad.
109 ///
110 /// This enum encodes the current step of the free monad computation. Both
111 /// variants hold type-erased values; the concrete type `A` is tracked by
112 /// [`PhantomData`] on the outer [`Free`] struct. The CatList of continuations
113 /// lives at the top level in [`Free`], not inside any variant.
114 #[document_type_parameters(
115 "The base functor. Requires [`Extract`] and [`Functor`] to match the struct-level bounds on [`Free`]; the `Suspend` variant itself only uses the [`Kind`](crate::kinds) trait (implied by `Functor`) for type application, and the `Extract` bound is needed for stack-safe `Drop`."
116 )]
117 pub enum FreeView<F>
118 where
119 F: Extract + Functor + 'static, {
120 /// A pure value (type-erased).
121 ///
122 /// This variant represents a computation that has finished and produced a value.
123 /// The actual type is tracked by `PhantomData<A>` on the enclosing [`Free`].
124 Return(TypeErasedValue),
125
126 /// A suspended computation (type-erased).
127 ///
128 /// This variant represents a computation that is suspended in the functor `F`.
129 /// The functor contains `Free<F, TypeErasedValue>` as the next step.
130 Suspend(
131 Apply!(<F as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'static, Free<F, TypeErasedValue>>),
132 ),
133 }
134
135 /// The result of stepping through a [`Free`] computation.
136 ///
137 /// Produced by [`Free::to_view`], this decomposes a `Free` into either
138 /// a final value or a suspended functor layer with all pending
139 /// continuations reattached. This provides a unified, single-step
140 /// decomposition that both [`Free::evaluate`] and [`Free::resume`]
141 /// delegate to.
142 #[document_type_parameters(
143 "The base functor. Requires [`Extract`] and [`Functor`] to match the struct-level bounds on [`Free`].",
144 "The result type of the computation."
145 )]
146 pub enum FreeStep<F, A>
147 where
148 F: Extract + Functor + 'static,
149 A: 'static, {
150 /// The computation completed with a final value.
151 Done(A),
152 /// The computation is suspended in the functor `F`.
153 /// The inner `Free` values have all pending continuations reattached.
154 Suspended(Apply!(<F as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'static, Free<F, A>>)),
155 }
156
157 /// The Free monad with O(1) bind via [`CatList`].
158 ///
159 /// This implementation follows ["Reflection without Remorse"](http://okmij.org/ftp/Haskell/zseq.pdf) to ensure
160 /// that left-associated binds do not degrade performance.
161 ///
162 /// # Internal representation
163 ///
164 /// A `Free` value consists of:
165 /// * A **view** (`FreeView<F>`) that is either a pure value (`Return`) or a suspended
166 /// computation (`Suspend`). Both variants hold type-erased values.
167 /// * A **continuation queue** (`CatList<Continuation<F>>`) that stores the chain of
168 /// pending `bind` operations with O(1) snoc and O(1) amortized uncons.
169 /// * A `PhantomData<A>` that tracks the concrete result type at the type level.
170 ///
171 /// Because the view is fully type-erased, `bind` is uniformly O(1) for all cases:
172 /// it simply appends the new continuation to the CatList without inspecting the view.
173 ///
174 /// # Linear consumption invariant
175 ///
176 /// `Free` values must be consumed exactly once, either by calling [`evaluate`](Free::evaluate),
177 /// [`bind`](Free::bind), [`erase_type`](Free::erase_type), or by being dropped. The `view`
178 /// field is wrapped in `Option` to enable a take-and-replace pattern (analogous to
179 /// `Cell::take`) so that methods like `evaluate` and `Drop` can move the view out without
180 /// leaving the struct in an invalid state. After the take, the `Option` is `None`, and any
181 /// subsequent access will panic with "Free value already consumed." This invariant is relied
182 /// upon by the `Drop` implementation to avoid double-freeing internal allocations.
183 ///
184 /// # HKT and Lifetime Limitations
185 ///
186 /// `Free` does not implement HKT traits (like `Functor`, `Monad`) from this library.
187 ///
188 /// ## The Conflict
189 /// * **The Traits**: The `Kind` trait implemented by the `Functor` hierarchy requires the type
190 /// constructor to accept *any* lifetime `'a` (e.g., `type Of<'a, A> = Free<F, A>`).
191 /// * **The Implementation**: This implementation uses [`Box<dyn Any>`] to type-erase continuations
192 /// for the "Reflection without Remorse" optimization. `dyn Any` strictly requires `A: 'static`.
193 ///
194 /// This creates an unresolvable conflict: `Free` cannot support non-static references (like `&'a str`),
195 /// so it cannot satisfy the `Kind` signature.
196 ///
197 /// ## Why not use the "Naive" Recursive Definition?
198 ///
199 /// A naive definition (`enum Free { Pure(A), Wrap(F<Box<Free<F, A>>>) }`) would support lifetimes
200 /// and HKT traits. However, it was rejected because:
201 /// 1. **Stack Safety**: `run` would not be stack-safe for deep computations.
202 /// 2. **Performance**: `bind` would be O(N), leading to quadratic complexity for sequences of binds.
203 ///
204 /// This implementation prioritizes **stack safety** and **O(1) bind** over HKT trait compatibility.
205 ///
206 /// # Consuming a `Free`: `evaluate` vs `fold_free`
207 ///
208 /// * [`evaluate`](Free::evaluate) runs the computation to completion using an iterative
209 /// loop. It requires the base functor `F` to implement [`Extract`], meaning each
210 /// suspended layer can be "unwrapped" to reveal the next step. Use this when you want
211 /// the final `A` value directly.
212 /// * [`fold_free`](Free::fold_free) interprets the `Free` into a different monad `G` via
213 /// a [`NaturalTransformation`] from `F` to `G`. Each suspended `F` layer is transformed
214 /// into `G`, and the results are threaded together using [`MonadRec::tail_rec_m`] for
215 /// stack-safe iteration. Use this when you want to translate the free structure into
216 /// another effect (e.g., `Option`, `Result`, or a custom interpreter).
217 ///
218 /// # Drop behavior
219 ///
220 /// Dropping a `Free` value iteratively dismantles the computation chain to avoid
221 /// stack overflow. For `Suspend` nodes, this requires extracting the inner `Free`
222 /// from the functor layer via [`Extract::extract`]. For functors like
223 /// [`ThunkBrand`], this means the thunk is evaluated during drop. Be aware that
224 /// dropping a partially-constructed `Free` chain may trigger deferred computations
225 /// and their side effects.
226 #[document_type_parameters(
227 "The base functor (must implement [`Extract`] and [`Functor`]). Many construction methods (`pure`, `bind`, `map`) only need `F: 'static` in principle, and functor-dependent methods (`wrap`, `lift_f`, `resume`, `fold_free`, `hoist_free`) only need `Functor`. The `Extract` bound is required at the struct level because the custom `Drop` implementation calls [`Extract::extract`] to iteratively dismantle `Suspend` nodes without overflowing the stack.",
228 "The result type."
229 )]
230 ///
231 pub struct Free<F, A>
232 where
233 F: Extract + Functor + 'static,
234 A: 'static, {
235 /// The current step of the computation (type-erased).
236 view: Option<FreeView<F>>,
237 /// The queue of pending continuations.
238 continuations: CatList<Continuation<F>>,
239 /// Phantom data tracking the concrete result type.
240 _marker: PhantomData<A>,
241 }
242
243 // -- Construction and composition --
244 //
245 // Methods in this block only need `F: 'static` in principle; they
246 // never call `Functor::map` or `Extract::extract`. The `Extract +
247 // Functor` bounds are inherited from the struct definition, which
248 // requires them for stack-safe `Drop` of `Suspend` nodes.
249 //
250 // Relaxing these bounds was investigated and is not feasible: Rust
251 // requires `Drop` impl bounds to match the struct bounds exactly,
252 // and the `Drop` impl needs `Extract` to iteratively dismantle
253 // `Suspend` nodes without stack overflow. Alternative approaches
254 // (ManuallyDrop with leaks, type-erased drop functions, split
255 // structs) all introduce unsoundness, leaks, or significant
256 // per-node overhead for marginal gain. This is a Rust-specific
257 // limitation; PureScript/Haskell avoid it via GC. The workaround
258 // for non-Extract functors is `fold_free`, which interprets into
259 // any `MonadRec` target.
260
261 #[document_type_parameters("The base functor.", "The result type.")]
262 #[document_parameters("The Free monad instance to operate on.")]
263 impl<F, A> Free<F, A>
264 where
265 F: Extract + Functor + 'static,
266 A: 'static,
267 {
268 /// Extracts the view and continuations, leaving `self` in a consumed
269 /// state (view `None`, continuations empty).
270 ///
271 /// Uses `Option::take` for the view and `std::mem::take` for the
272 /// continuations, both of which leave valid sentinel values behind.
273 /// The custom `Drop` implementation handles the consumed state correctly.
274 #[document_signature]
275 #[document_returns("A tuple of the view and continuation queue, moved out of this `Free`.")]
276 #[document_examples]
277 ///
278 /// ```
279 /// use fp_library::{
280 /// brands::*,
281 /// types::*,
282 /// };
283 ///
284 /// // `take_parts` is internal; `evaluate` is the public API that uses it.
285 /// let free = Free::<ThunkBrand, _>::pure(42);
286 /// assert_eq!(free.evaluate(), 42);
287 /// ```
288 fn take_parts(&mut self) -> (Option<FreeView<F>>, CatList<Continuation<F>>) {
289 let view = self.view.take();
290 let conts = std::mem::take(&mut self.continuations);
291 (view, conts)
292 }
293
294 /// Changes the phantom type parameter without adding any continuations.
295 ///
296 /// This is an internal-only operation used by `bind` continuations where
297 /// the caller guarantees the stored type matches the new phantom. Unlike
298 /// the public [`erase_type`](Free::erase_type), this does not append a
299 /// rebox continuation, so the caller must ensure type safety.
300 #[document_signature]
301 #[document_type_parameters("The target phantom type.")]
302 #[document_returns("The same `Free` with a different phantom type parameter.")]
303 #[document_examples]
304 ///
305 /// ```
306 /// use fp_library::{
307 /// brands::*,
308 /// types::*,
309 /// };
310 ///
311 /// // `cast_phantom` is internal; `bind` is the public API that uses it.
312 /// let free = Free::<ThunkBrand, _>::pure(42).bind(|x| Free::pure(x + 1));
313 /// assert_eq!(free.evaluate(), 43);
314 /// ```
315 fn cast_phantom<B: 'static>(mut self) -> Free<F, B> {
316 let (view, conts) = self.take_parts();
317 Free {
318 view,
319 continuations: conts,
320 _marker: PhantomData,
321 }
322 }
323
324 /// Creates a pure `Free` value.
325 #[document_signature]
326 ///
327 #[document_parameters("The value to wrap.")]
328 ///
329 #[document_returns("A `Free` computation that produces `a`.")]
330 ///
331 #[inline]
332 #[document_examples]
333 ///
334 /// ```
335 /// use fp_library::{
336 /// brands::*,
337 /// types::*,
338 /// };
339 ///
340 /// let free = Free::<ThunkBrand, _>::pure(42);
341 /// assert_eq!(free.evaluate(), 42);
342 /// ```
343 pub fn pure(a: A) -> Self {
344 Free {
345 view: Some(FreeView::Return(Box::new(a) as TypeErasedValue)),
346 continuations: CatList::empty(),
347 _marker: PhantomData,
348 }
349 }
350
351 /// Monadic bind with O(1) complexity.
352 ///
353 /// This is uniformly O(1) for all cases: the new continuation is simply
354 /// appended to the CatList without inspecting the view.
355 #[document_signature]
356 ///
357 #[document_type_parameters("The result type of the new computation.")]
358 ///
359 #[document_parameters("The function to apply to the result of this computation.")]
360 ///
361 #[document_returns("A new `Free` computation that chains `f` after this computation.")]
362 ///
363 #[document_examples]
364 ///
365 /// ```
366 /// use fp_library::{
367 /// brands::*,
368 /// types::*,
369 /// };
370 ///
371 /// let free = Free::<ThunkBrand, _>::pure(42).bind(|x| Free::pure(x + 1));
372 /// assert_eq!(free.evaluate(), 43);
373 /// ```
374 pub fn bind<B: 'static>(
375 mut self,
376 f: impl FnOnce(A) -> Free<F, B> + 'static,
377 ) -> Free<F, B> {
378 // Type-erase the continuation
379 let erased_f: Continuation<F> = Box::new(move |val: TypeErasedValue| {
380 // INVARIANT: type is maintained by internal invariant; mismatch indicates a bug
381 #[expect(clippy::expect_used, reason = "Type maintained by internal invariant")]
382 let a: A = *val.downcast().expect("Type mismatch in Free::bind");
383 let free_b: Free<F, B> = f(a);
384 // Use cast_phantom (not erase_type) to avoid adding a rebox
385 // continuation. The view already stores TypeErasedValue, and
386 // the next continuation in the chain knows the correct type to
387 // downcast to.
388 free_b.cast_phantom()
389 });
390
391 // Uniformly O(1): just move the view and snoc the continuation.
392 // The view is already type-erased, so it can be moved directly.
393 let (view, conts) = self.take_parts();
394 Free {
395 view,
396 continuations: conts.snoc(erased_f),
397 _marker: PhantomData,
398 }
399 }
400
401 /// Functor map: transforms the result without changing structure.
402 ///
403 /// Implemented via [`bind`](Free::bind) and [`pure`](Free::pure),
404 /// keeping the internal representation simple with fewer variants.
405 #[document_signature]
406 ///
407 #[document_type_parameters("The result type of the mapping function.")]
408 ///
409 #[document_parameters("The function to apply to the result of this computation.")]
410 ///
411 #[document_returns("A new `Free` computation with the transformed result.")]
412 ///
413 #[document_examples]
414 ///
415 /// ```
416 /// use fp_library::{
417 /// brands::*,
418 /// types::*,
419 /// };
420 ///
421 /// let free = Free::<ThunkBrand, _>::pure(10).map(|x| x * 2);
422 /// assert_eq!(free.evaluate(), 20);
423 /// ```
424 pub fn map<B: 'static>(
425 self,
426 f: impl FnOnce(A) -> B + 'static,
427 ) -> Free<F, B> {
428 self.bind(move |a| Free::pure(f(a)))
429 }
430
431 /// Converts to type-erased form.
432 ///
433 /// With the CatList-paired representation, the view is already type-erased,
434 /// so this operation simply changes the phantom type parameter.
435 #[document_signature]
436 #[document_returns(
437 "A `Free` computation where the result type has been erased to `Box<dyn Any>`."
438 )]
439 #[document_examples]
440 ///
441 /// ```
442 /// use fp_library::{
443 /// brands::*,
444 /// types::*,
445 /// };
446 /// let free = Free::<ThunkBrand, _>::pure(42);
447 /// let erased = free.erase_type();
448 /// assert!(erased.evaluate().is::<i32>());
449 /// ```
450 pub fn erase_type(mut self) -> Free<F, TypeErasedValue> {
451 let (view, conts) = self.take_parts();
452 // Append a continuation that re-boxes the value so that the outer
453 // phantom type (TypeErasedValue = Box<dyn Any>) matches the stored
454 // type. Without this, evaluate would try to downcast Box<dyn Any>
455 // containing A to Box<dyn Any>, which fails when A != Box<dyn Any>.
456 let rebox_cont: Continuation<F> = Box::new(|val: TypeErasedValue| Free {
457 view: Some(FreeView::Return(Box::new(val) as TypeErasedValue)),
458 continuations: CatList::empty(),
459 _marker: PhantomData,
460 });
461 Free {
462 view,
463 continuations: conts.snoc(rebox_cont),
464 _marker: PhantomData,
465 }
466 }
467
468 /// Converts to boxed type-erased form.
469 #[document_signature]
470 #[document_returns("A boxed `Free` computation where the result type has been erased.")]
471 #[document_examples]
472 ///
473 /// ```
474 /// use fp_library::{
475 /// brands::*,
476 /// types::*,
477 /// };
478 /// let free = Free::<ThunkBrand, _>::pure(42);
479 /// let boxed = free.boxed_erase_type();
480 /// assert!(boxed.evaluate().is::<i32>());
481 /// ```
482 pub fn boxed_erase_type(self) -> Box<Free<F, TypeErasedValue>> {
483 Box::new(self.erase_type())
484 }
485 }
486
487 // -- Functor-dependent operations --
488 //
489 // Methods in this block call `Functor::map` (`F::map`) and thus
490 // require `F: Functor`. They do NOT call `Extract::extract`; the
491 // `Extract` bound is inherited from the struct definition.
492
493 #[document_type_parameters("The base functor.", "The result type.")]
494 #[document_parameters("The Free monad instance to operate on.")]
495 impl<F, A> Free<F, A>
496 where
497 F: Extract + Functor + 'static,
498 A: 'static,
499 {
500 /// Creates a suspended computation from a functor value.
501 #[document_signature]
502 ///
503 #[document_parameters("The functor value containing the next step.")]
504 ///
505 #[document_returns("A `Free` computation that performs the effect `fa`.")]
506 ///
507 #[document_examples]
508 ///
509 /// ```
510 /// use fp_library::{
511 /// brands::*,
512 /// types::*,
513 /// };
514 ///
515 /// let eval = Thunk::new(|| Free::pure(42));
516 /// let free = Free::<ThunkBrand, _>::wrap(eval);
517 /// assert_eq!(free.evaluate(), 42);
518 /// ```
519 pub fn wrap(
520 fa: Apply!(<F as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'static, Free<F, A>>)
521 ) -> Self {
522 // Type-erase the inner Free values in the functor using F::map.
523 let erased_fa = F::map(
524 |inner: Free<F, A>| -> Free<F, TypeErasedValue> { inner.cast_phantom() },
525 fa,
526 );
527 Free {
528 view: Some(FreeView::Suspend(erased_fa)),
529 continuations: CatList::empty(),
530 _marker: PhantomData,
531 }
532 }
533
534 /// Lifts a functor value into the Free monad.
535 ///
536 /// This is the primary way to inject effects into Free monad computations.
537 /// Equivalent to PureScript's `liftF` and Haskell's `liftF`.
538 #[document_signature]
539 ///
540 /// ### Implementation
541 ///
542 /// ```text
543 /// liftF fa = wrap (map pure fa)
544 /// ```
545 #[document_parameters("The functor value to lift.")]
546 ///
547 #[document_returns("A `Free` computation that performs the effect and returns the result.")]
548 ///
549 #[document_examples]
550 ///
551 /// ```
552 /// use fp_library::{
553 /// brands::*,
554 /// types::*,
555 /// };
556 ///
557 /// // Lift a simple computation
558 /// let thunk = Thunk::new(|| 42);
559 /// let free = Free::<ThunkBrand, _>::lift_f(thunk);
560 /// assert_eq!(free.evaluate(), 42);
561 ///
562 /// // Build a computation from raw effects
563 /// let computation = Free::<ThunkBrand, _>::lift_f(Thunk::new(|| 10))
564 /// .bind(|x| Free::lift_f(Thunk::new(move || x * 2)))
565 /// .bind(|x| Free::lift_f(Thunk::new(move || x + 5)));
566 /// assert_eq!(computation.evaluate(), 25);
567 /// ```
568 pub fn lift_f(fa: Apply!(<F as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'static, A>)) -> Self {
569 // Map the value to a pure Free, then wrap it
570 Free::wrap(F::map(Free::pure, fa))
571 }
572
573 /// Decomposes this `Free` computation into a single [`FreeStep`].
574 ///
575 /// Iteratively applies pending continuations until the computation
576 /// reaches either a final value ([`FreeStep::Done`]) or a suspended
577 /// functor layer ([`FreeStep::Suspended`]). In the `Suspended` case,
578 /// all remaining continuations are reattached to the inner `Free`
579 /// values via [`Functor::map`], so the caller receives a fully
580 /// self-contained layer that can be further interpreted.
581 ///
582 /// This is the shared core that both [`evaluate`](Free::evaluate) and
583 /// [`resume`](Free::resume) delegate to.
584 #[document_signature]
585 ///
586 #[document_returns(
587 "[`FreeStep::Done(a)`](FreeStep::Done) if the computation is complete, or [`FreeStep::Suspended(fa)`](FreeStep::Suspended) if it is suspended in the functor `F`."
588 )]
589 ///
590 #[document_examples]
591 ///
592 /// ```
593 /// use fp_library::{
594 /// brands::*,
595 /// types::*,
596 /// };
597 ///
598 /// // Pure value yields Done
599 /// let free = Free::<ThunkBrand, _>::pure(42);
600 /// match free.to_view() {
601 /// FreeStep::Done(a) => assert_eq!(a, 42),
602 /// FreeStep::Suspended(_) => panic!("expected Done"),
603 /// }
604 ///
605 /// // Wrapped value yields Suspended
606 /// let free = Free::<ThunkBrand, _>::wrap(Thunk::new(|| Free::pure(99)));
607 /// assert!(matches!(free.to_view(), FreeStep::Suspended(_)));
608 /// ```
609 #[expect(
610 clippy::expect_used,
611 reason = "Free values consumed exactly once; double consumption is a bug"
612 )]
613 pub fn to_view(mut self) -> FreeStep<F, A> {
614 let (view, continuations) = self.take_parts();
615
616 // INVARIANT: Free values are used exactly once; double consumption indicates a bug
617 let mut current_view = view.expect("Free value already consumed");
618 let mut conts = continuations;
619
620 loop {
621 match current_view {
622 FreeView::Return(val) => {
623 match conts.uncons() {
624 Some((continuation, rest)) => {
625 let mut next = continuation(val);
626 let (next_view, next_conts) = next.take_parts();
627 // INVARIANT: continuation returns a valid Free
628 current_view =
629 next_view.expect("Free value already consumed (continuation)");
630 conts = next_conts.append(rest);
631 }
632 None => {
633 // No more continuations; we have a final pure value.
634 // INVARIANT: type is maintained by internal invariant
635 return FreeStep::Done(
636 *val.downcast::<A>()
637 .expect("Type mismatch in Free::to_view final downcast"),
638 );
639 }
640 }
641 }
642
643 FreeView::Suspend(fa) => {
644 // We have a suspended computation. Rebuild the
645 // Free<F, A> from the type-erased Free<F, TypeErasedValue>
646 // by re-attaching the remaining continuations.
647
648 // Append a final downcast continuation that converts
649 // TypeErasedValue back to the concrete type A.
650 let downcast_cont: Continuation<F> =
651 Box::new(move |val: TypeErasedValue| {
652 // INVARIANT: type is maintained by internal invariant
653 let a: A = *val
654 .downcast()
655 .expect("Type mismatch in Free::to_view downcast");
656 Free::<F, A>::pure(a).cast_phantom()
657 });
658 let all_conts = conts.snoc(downcast_cont);
659
660 // Use Cell to move the CatList into the Fn closure (called exactly once).
661 let remaining = std::cell::Cell::new(Some(all_conts));
662 let typed_fa = F::map(
663 move |mut inner_free: Free<F, TypeErasedValue>| {
664 // INVARIANT: functors call map exactly once per element
665 let conts_for_inner = remaining
666 .take()
667 .expect("Free::to_view map called more than once");
668 let (v, c) = inner_free.take_parts();
669 Free {
670 view: v,
671 continuations: c.append(conts_for_inner),
672 _marker: PhantomData,
673 }
674 },
675 fa,
676 );
677 return FreeStep::Suspended(typed_fa);
678 }
679 }
680 }
681 }
682
683 /// Decomposes this `Free` computation into one step.
684 ///
685 /// Returns `Ok(a)` if the computation is a pure value, or
686 /// `Err(f_free)` if the computation is suspended in the functor `F`,
687 /// where `f_free` contains the next `Free` computation wrapped in `F`.
688 ///
689 /// Delegates to [`to_view`](Free::to_view) and converts the resulting
690 /// [`FreeStep`] into a `Result`.
691 #[document_signature]
692 ///
693 #[document_returns(
694 "`Ok(a)` if the computation is a pure value, `Err(fa)` if it is a suspended computation."
695 )]
696 ///
697 #[document_examples]
698 ///
699 /// ```
700 /// use fp_library::{
701 /// brands::*,
702 /// types::*,
703 /// };
704 ///
705 /// // Pure returns Ok
706 /// let free = Free::<ThunkBrand, _>::pure(42);
707 /// assert!(matches!(free.resume(), Ok(42)));
708 ///
709 /// // Wrap returns Err containing the functor layer
710 /// let free = Free::<ThunkBrand, _>::wrap(Thunk::new(|| Free::pure(99)));
711 /// assert!(free.resume().is_err());
712 /// ```
713 pub fn resume(
714 self
715 ) -> Result<A, Apply!(<F as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'static, Free<F, A>>)> {
716 match self.to_view() {
717 FreeStep::Done(a) => Ok(a),
718 FreeStep::Suspended(fa) => Err(fa),
719 }
720 }
721
722 /// Interprets this `Free` monad into a target monad `G` using a natural transformation.
723 ///
724 /// This is the standard `foldFree` operation from Haskell/PureScript. It uses
725 /// [`MonadRec::tail_rec_m`] to iteratively process the free structure, applying the
726 /// natural transformation at each suspended layer to convert from functor `F` into
727 /// monad `G`.
728 ///
729 /// For `Pure(a)`, returns `G::pure(ControlFlow::Break(a))`.
730 /// For `Wrap(fa)`, applies the natural transformation to get `G<Free<F, A>>`,
731 /// then maps to `ControlFlow::Continue` to continue the iteration.
732 ///
733 /// ### Stack Safety
734 ///
735 /// This implementation is stack-safe as long as the target monad `G` implements
736 /// [`MonadRec`] with a stack-safe `tail_rec_m`. The iteration is driven by
737 /// `G::tail_rec_m` rather than actual recursion, so deeply nested `Free`
738 /// structures will not overflow the stack.
739 #[document_signature]
740 ///
741 #[document_type_parameters("The target monad brand.")]
742 ///
743 #[document_parameters("The natural transformation from `F` to `G`.")]
744 ///
745 #[document_returns("The result of interpreting this free computation in monad `G`.")]
746 ///
747 #[document_examples]
748 ///
749 /// ```
750 /// use fp_library::{
751 /// Apply,
752 /// brands::*,
753 /// classes::*,
754 /// kinds::*,
755 /// types::*,
756 /// };
757 ///
758 /// // Define a natural transformation from Thunk to Option
759 /// #[derive(Clone)]
760 /// struct ThunkToOption;
761 /// impl NaturalTransformation<ThunkBrand, OptionBrand> for ThunkToOption {
762 /// fn transform<'a, A: 'a>(
763 /// &self,
764 /// fa: Thunk<'a, A>,
765 /// ) -> Option<A> {
766 /// Some(fa.evaluate())
767 /// }
768 /// }
769 ///
770 /// let free = Free::<ThunkBrand, _>::pure(42);
771 /// let result: Option<i32> = free.fold_free::<OptionBrand>(ThunkToOption);
772 /// assert_eq!(result, Some(42));
773 /// ```
774 pub fn fold_free<G>(
775 self,
776 nt: impl NaturalTransformation<F, G> + Clone + 'static,
777 ) -> Apply!(<G as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'static, A>)
778 where
779 G: MonadRec + 'static, {
780 G::tail_rec_m(
781 move |free: Free<F, A>| match free.resume() {
782 Ok(a) => G::pure(ControlFlow::Break(a)),
783 Err(fa) => {
784 // fa: F<Free<F, A>>
785 // Transform F<Free<F, A>> into G<Free<F, A>> using the natural
786 // transformation, then map to ControlFlow::Continue to continue iteration.
787 let ga: Apply!(
788 <G as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<
789 'static,
790 Free<F, A>,
791 >
792 ) = nt.transform(fa);
793 G::map(|inner_free: Free<F, A>| ControlFlow::Continue(inner_free), ga)
794 }
795 },
796 self,
797 )
798 }
799
800 /// Transforms the functor layer of this `Free` monad using a natural transformation.
801 ///
802 /// Converts `Free<F, A>` into `Free<G, A>` by applying a natural transformation
803 /// to each suspended functor layer. This is the standard `hoistFree` operation from
804 /// PureScript/Haskell.
805 ///
806 /// ### Stack Safety
807 ///
808 /// This method is stack-safe for arbitrarily deep `Suspend` chains. Rather than
809 /// recursively mapping `hoist_free` over inner layers during construction, it uses
810 /// [`lift_f`](Free::lift_f) and [`bind`](Free::bind) to defer each layer's
811 /// transformation into a continuation stored in the `CatList`. The actual work
812 /// happens inside [`evaluate`](Free::evaluate)'s iterative loop, so the call
813 /// stack depth is O(1) regardless of how many `Suspend` layers the `Free` contains.
814 #[document_signature]
815 ///
816 #[document_type_parameters("The target functor brand.")]
817 ///
818 #[document_parameters("The natural transformation from `F` to `G`.")]
819 ///
820 #[document_returns("A `Free` computation over functor `G` with the same result.")]
821 ///
822 #[document_examples]
823 ///
824 /// ```
825 /// use fp_library::{
826 /// brands::*,
827 /// classes::NaturalTransformation,
828 /// types::*,
829 /// };
830 ///
831 /// // Identity natural transformation (Thunk to Thunk)
832 /// #[derive(Clone)]
833 /// struct ThunkId;
834 /// impl NaturalTransformation<ThunkBrand, ThunkBrand> for ThunkId {
835 /// fn transform<'a, A: 'a>(
836 /// &self,
837 /// fa: Thunk<'a, A>,
838 /// ) -> Thunk<'a, A> {
839 /// fa
840 /// }
841 /// }
842 ///
843 /// let free = Free::<ThunkBrand, _>::lift_f(Thunk::new(|| 42));
844 /// let hoisted: Free<ThunkBrand, i32> = free.hoist_free(ThunkId);
845 /// assert_eq!(hoisted.evaluate(), 42);
846 /// ```
847 pub fn hoist_free<G: Extract + Functor + 'static>(
848 self,
849 nt: impl NaturalTransformation<F, G> + Clone + 'static,
850 ) -> Free<G, A> {
851 match self.resume() {
852 Ok(a) => Free::pure(a),
853 Err(fa) => {
854 // fa: F<Free<F, A>>
855 // Transform F<Free<F, A>> into G<Free<F, A>>
856 let nt_clone = nt.clone();
857 let ga = nt.transform(fa);
858 // Lift G<Free<F, A>> into Free<G, Free<F, A>> using lift_f,
859 // then bind to recursively hoist. The bind closure is stored
860 // in the CatList and only executed during evaluate's iterative
861 // loop, so this does not grow the call stack.
862 Free::<G, Free<F, A>>::lift_f(ga)
863 .bind(move |inner: Free<F, A>| inner.hoist_free(nt_clone))
864 }
865 }
866 }
867
868 /// Interprets `Free<F, A>` into `Free<G, A>` by substituting each
869 /// suspended `F` layer with a `Free<G, _>` computation.
870 ///
871 /// This is the standard `substFree` from PureScript. It is similar to
872 /// [`hoist_free`](Free::hoist_free) but more powerful: instead of a
873 /// plain natural transformation `F ~> G`, the callback returns
874 /// `Free<G, _>`, allowing each `F` layer to expand into an entire
875 /// `Free<G>` sub-computation.
876 ///
877 /// ### Stack Safety
878 ///
879 /// This method is stack-safe for arbitrarily deep `Suspend` chains,
880 /// following the same pattern as [`hoist_free`](Free::hoist_free). The
881 /// recursive `substitute_free` call is inside a [`bind`](Free::bind)
882 /// closure deferred into the CatList. Each invocation does O(1) work
883 /// (one [`to_view`](Free::to_view), one natural transformation, one
884 /// `bind`) and returns immediately. The actual work happens inside
885 /// [`evaluate`](Free::evaluate)'s iterative loop, so the call stack
886 /// depth is O(1) regardless of `Suspend` depth.
887 #[document_signature]
888 ///
889 #[document_type_parameters("The target functor brand.")]
890 ///
891 #[document_parameters(
892 "A function that transforms each suspended `F` layer into a `Free<G, _>` computation."
893 )]
894 ///
895 #[document_returns(
896 "A `Free<G, A>` computation where every `F` layer has been substituted."
897 )]
898 ///
899 #[document_examples]
900 ///
901 /// ```
902 /// use fp_library::{
903 /// brands::*,
904 /// classes::NaturalTransformation,
905 /// types::*,
906 /// };
907 ///
908 /// // Identity substitution: each Thunk layer becomes a Free<ThunkBrand, _>
909 /// let free = Free::<ThunkBrand, _>::lift_f(Thunk::new(|| 42));
910 /// let substituted: Free<ThunkBrand, i32> =
911 /// free.substitute_free(|thunk: Thunk<'static, Free<ThunkBrand, i32>>| {
912 /// Free::<ThunkBrand, _>::lift_f(thunk)
913 /// });
914 /// assert_eq!(substituted.evaluate(), 42);
915 /// ```
916 pub fn substitute_free<G: Extract + Functor + 'static>(
917 self,
918 nt: impl Fn(
919 Apply!(
920 <F as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'static, Free<F, A>>
921 ),
922 ) -> Free<G, Free<F, A>>
923 + Clone
924 + 'static,
925 ) -> Free<G, A> {
926 match self.to_view() {
927 FreeStep::Done(a) => Free::pure(a),
928 FreeStep::Suspended(fa) => {
929 // nt transforms the F-layer into Free<G, Free<F, A>>
930 let free_g: Free<G, Free<F, A>> = nt.clone()(fa);
931 // bind to recurse: for each inner Free<F, A>, continue substituting
932 let nt_clone = nt;
933 free_g.bind(move |inner| inner.substitute_free(nt_clone.clone()))
934 }
935 }
936 }
937 }
938
939 // -- Evaluation (requires Extract) --
940 //
941 // This method calls `Extract::extract` and thus genuinely requires
942 // `F: Extract + Functor`. The `Drop` implementation also needs
943 // `Extract` for the same reason (stack-safe dismantling of `Suspend`
944 // nodes).
945
946 #[document_type_parameters("The base functor.", "The result type.")]
947 #[document_parameters("The Free monad instance to operate on.")]
948 impl<F, A> Free<F, A>
949 where
950 F: Extract + Functor + 'static,
951 A: 'static,
952 {
953 /// Executes the Free computation, returning the final result.
954 ///
955 /// Uses [`to_view`](Free::to_view) to iteratively step through the
956 /// computation: each `Suspended` layer is unwrapped via
957 /// [`Extract::extract`] and fed back into the loop, while `Done`
958 /// returns the final value. The continuation chain is collapsed by
959 /// `to_view`, so this outer loop only sees functor layers.
960 #[document_signature]
961 ///
962 #[document_returns("The final result of the computation.")]
963 ///
964 #[document_examples]
965 ///
966 /// ```
967 /// use fp_library::{
968 /// brands::*,
969 /// types::*,
970 /// };
971 ///
972 /// let free = Free::<ThunkBrand, _>::pure(42);
973 /// assert_eq!(free.evaluate(), 42);
974 /// ```
975 pub fn evaluate(self) -> A {
976 let mut current = self;
977 loop {
978 match current.to_view() {
979 FreeStep::Done(a) => return a,
980 FreeStep::Suspended(fa) => {
981 current = <F as Extract>::extract(fa);
982 }
983 }
984 }
985 }
986 }
987
988 #[document_type_parameters("The base functor.", "The result type.")]
989 #[document_parameters("The free monad instance to drop.")]
990 impl<F, A> Drop for Free<F, A>
991 where
992 F: Extract + Functor + 'static,
993 A: 'static,
994 {
995 #[document_signature]
996 #[document_examples]
997 ///
998 /// ```
999 /// use fp_library::{
1000 /// brands::*,
1001 /// types::*,
1002 /// };
1003 /// {
1004 /// let _free = Free::<ThunkBrand, _>::pure(42);
1005 /// } // drop called here
1006 /// assert!(true);
1007 /// ```
1008 fn drop(&mut self) {
1009 // Take the view out so we can iteratively dismantle the chain
1010 // instead of relying on recursive Drop, which would overflow the stack
1011 // for deep computations (both continuation chains and Suspend chains).
1012 let mut worklist: Vec<FreeView<F>> = Vec::new();
1013
1014 if let Some(view) = self.view.take() {
1015 worklist.push(view);
1016 }
1017
1018 // Drain the top-level continuations iteratively. Each
1019 // continuation is a Box<dyn FnOnce> that may capture Free
1020 // values. By consuming them one at a time via uncons, we
1021 // let each boxed closure drop without building stack depth.
1022 let mut top_conts = std::mem::take(&mut self.continuations);
1023 while let Some((_continuation, rest)) = top_conts.uncons() {
1024 top_conts = rest;
1025 }
1026
1027 while let Some(view) = worklist.pop() {
1028 match view {
1029 FreeView::Return(_) => {
1030 // Trivially dropped, no nested Free values.
1031 }
1032
1033 FreeView::Suspend(fa) => {
1034 // The functor layer contains a `Free<F, TypeErasedValue>` inside.
1035 // If we let it drop recursively, deeply nested Suspend chains will
1036 // overflow the stack. Instead, we use `Extract::extract`
1037 // to eagerly extract the inner `Free`, then push its view onto
1038 // the worklist for iterative dismantling.
1039 let mut extracted: Free<F, TypeErasedValue> = <F as Extract>::extract(fa);
1040 if let Some(inner_view) = extracted.view.take() {
1041 worklist.push(inner_view);
1042 }
1043 // Drain the extracted node's continuations iteratively.
1044 let mut inner_conts = std::mem::take(&mut extracted.continuations);
1045 while let Some((_continuation, rest)) = inner_conts.uncons() {
1046 inner_conts = rest;
1047 }
1048 }
1049 }
1050 }
1051 }
1052 }
1053
1054 #[document_type_parameters("The result type.")]
1055 impl<A: 'static> Deferrable<'static> for Free<ThunkBrand, A> {
1056 /// Creates a `Free` computation from a thunk.
1057 ///
1058 /// This delegates to `Free::wrap` and `Thunk::new`.
1059 #[document_signature]
1060 ///
1061 #[document_parameters("A thunk that produces the free computation.")]
1062 ///
1063 #[document_returns("The deferred free computation.")]
1064 ///
1065 #[document_examples]
1066 ///
1067 /// ```
1068 /// use fp_library::{
1069 /// brands::*,
1070 /// classes::Deferrable,
1071 /// functions::*,
1072 /// types::*,
1073 /// };
1074 ///
1075 /// let task: Free<ThunkBrand, i32> = Deferrable::defer(|| Free::pure(42));
1076 /// assert_eq!(task.evaluate(), 42);
1077 /// ```
1078 fn defer(f: impl FnOnce() -> Self + 'static) -> Self
1079 where
1080 Self: Sized, {
1081 Self::wrap(Thunk::new(f))
1082 }
1083 }
1084}
1085pub use inner::*;
1086
1087#[cfg(test)]
1088#[expect(
1089 clippy::unwrap_used,
1090 clippy::unreachable,
1091 clippy::panic,
1092 reason = "Tests use panicking operations for brevity and clarity"
1093)]
1094mod tests {
1095 use {
1096 super::*,
1097 crate::{
1098 brands::{
1099 OptionBrand,
1100 ThunkBrand,
1101 },
1102 classes::natural_transformation::NaturalTransformation,
1103 types::thunk::Thunk,
1104 },
1105 };
1106
1107 /// Tests `Free::pure`.
1108 ///
1109 /// **What it tests:** Verifies that `pure` creates a computation that simply returns the provided value.
1110 /// **How it tests:** Constructs a `Free::pure(42)` and runs it, asserting the result is 42.
1111 #[test]
1112 fn test_free_pure() {
1113 let free = Free::<ThunkBrand, _>::pure(42);
1114 assert_eq!(free.evaluate(), 42);
1115 }
1116
1117 /// Tests `Free::wrap`.
1118 ///
1119 /// **What it tests:** Verifies that `wrap` creates a computation from a suspended effect.
1120 /// **How it tests:** Wraps a `Free::pure(42)` inside a `Thunk`, wraps it into a `Free`, and runs it to ensure it unwraps correctly.
1121 #[test]
1122 fn test_free_wrap() {
1123 let eval = Thunk::new(|| Free::pure(42));
1124 let free = Free::<ThunkBrand, _>::wrap(eval);
1125 assert_eq!(free.evaluate(), 42);
1126 }
1127
1128 /// Tests `Free::bind`.
1129 ///
1130 /// **What it tests:** Verifies that `bind` correctly chains computations and passes values between them.
1131 /// **How it tests:** Chains `pure(42) -> bind(+1) -> bind(*2)` and asserts the result is (42+1)*2 = 86.
1132 #[test]
1133 fn test_free_bind() {
1134 let free =
1135 Free::<ThunkBrand, _>::pure(42).bind(|x| Free::pure(x + 1)).bind(|x| Free::pure(x * 2));
1136 assert_eq!(free.evaluate(), 86);
1137 }
1138
1139 /// Tests stack safety of `Free::evaluate`.
1140 ///
1141 /// **What it tests:** Verifies that `run` can handle deep recursion without stack overflow (trampolining).
1142 /// **How it tests:** Creates a recursive `count_down` function that builds a chain of 100,000 `bind` calls.
1143 /// If the implementation were not stack-safe, this would crash with a stack overflow.
1144 #[test]
1145 fn test_free_stack_safety() {
1146 fn count_down(n: i32) -> Free<ThunkBrand, i32> {
1147 if n == 0 { Free::pure(0) } else { Free::pure(n).bind(|n| count_down(n - 1)) }
1148 }
1149
1150 // 100,000 iterations should overflow stack if not safe
1151 let free = count_down(100_000);
1152 assert_eq!(free.evaluate(), 0);
1153 }
1154
1155 /// Tests stack safety of `Free::drop`.
1156 ///
1157 /// **What it tests:** Verifies that dropping a deep `Free` computation does not cause a stack overflow.
1158 /// **How it tests:** Constructs a deep `Free` chain (similar to `test_free_stack_safety`) and lets it go out of scope.
1159 #[test]
1160 fn test_free_drop_safety() {
1161 fn count_down(n: i32) -> Free<ThunkBrand, i32> {
1162 if n == 0 { Free::pure(0) } else { Free::pure(n).bind(|n| count_down(n - 1)) }
1163 }
1164
1165 // Construct a deep chain but DO NOT run it.
1166 // When `free` goes out of scope, `Drop` should handle it iteratively.
1167 let _free = count_down(100_000);
1168 }
1169
1170 /// Tests `Free::bind` on a `Wrap` variant.
1171 ///
1172 /// **What it tests:** Verifies that `bind` works correctly when applied to a suspended computation (`Wrap`).
1173 /// **How it tests:** Creates a `Wrap` (via `wrap`) and `bind`s it.
1174 #[test]
1175 fn test_free_bind_on_wrap() {
1176 let eval = Thunk::new(|| Free::pure(42));
1177 let free = Free::<ThunkBrand, _>::wrap(eval).bind(|x| Free::pure(x + 1));
1178 assert_eq!(free.evaluate(), 43);
1179 }
1180
1181 /// Tests `Free::lift_f`.
1182 ///
1183 /// **What it tests:** Verifies that `lift_f` correctly lifts a functor value into the Free monad.
1184 /// **How it tests:** Lifts a simple thunk and verifies the result.
1185 #[test]
1186 fn test_free_lift_f() {
1187 let thunk = Thunk::new(|| 42);
1188 let free = Free::<ThunkBrand, _>::lift_f(thunk);
1189 assert_eq!(free.evaluate(), 42);
1190 }
1191
1192 /// Tests `Free::lift_f` with bind.
1193 ///
1194 /// **What it tests:** Verifies that `lift_f` can be used to build computations with `bind`.
1195 /// **How it tests:** Chains multiple `lift_f` calls with `bind`.
1196 #[test]
1197 fn test_free_lift_f_with_bind() {
1198 let computation = Free::<ThunkBrand, _>::lift_f(Thunk::new(|| 10))
1199 .bind(|x| Free::<ThunkBrand, _>::lift_f(Thunk::new(move || x * 2)))
1200 .bind(|x| Free::<ThunkBrand, _>::lift_f(Thunk::new(move || x + 5)));
1201 assert_eq!(computation.evaluate(), 25);
1202 }
1203
1204 /// Tests `Free::resume` on a `Pure` variant.
1205 ///
1206 /// **What it tests:** Verifies that `resume` on a pure value returns `Ok(value)`.
1207 /// **How it tests:** Creates a `Free::pure(42)` and asserts `resume()` returns `Ok(42)`.
1208 #[test]
1209 fn test_free_resume_pure() {
1210 let free = Free::<ThunkBrand, _>::pure(42);
1211 assert!(matches!(free.resume(), Ok(42)));
1212 }
1213
1214 /// Tests `Free::resume` on a `Wrap` variant.
1215 ///
1216 /// **What it tests:** Verifies that `resume` on a suspended computation returns `Err(functor_layer)`.
1217 /// **How it tests:** Creates a `Free::wrap(...)` and asserts `resume()` returns `Err(_)`,
1218 /// then evaluates the inner `Free` to verify the value is preserved.
1219 #[test]
1220 fn test_free_resume_wrap() {
1221 let free = Free::<ThunkBrand, _>::wrap(Thunk::new(|| Free::pure(99)));
1222 let result = free.resume();
1223 assert!(result.is_err());
1224 // Evaluate the inner thunk to verify the value
1225 let thunk = result.unwrap_err();
1226 let inner_free: Free<ThunkBrand, i32> = thunk.evaluate();
1227 assert_eq!(inner_free.evaluate(), 99);
1228 }
1229
1230 /// Tests `Free::resume` on a `Bind` variant.
1231 ///
1232 /// **What it tests:** Verifies that `resume` correctly collapses bind chains.
1233 /// **How it tests:** Creates a `pure(10).bind(|x| pure(x + 5))` and checks
1234 /// that resume returns `Ok(15)` after collapsing the bind.
1235 #[test]
1236 fn test_free_resume_bind() {
1237 let free = Free::<ThunkBrand, _>::pure(10).bind(|x: i32| Free::pure(x + 5));
1238 assert!(matches!(free.resume(), Ok(15)));
1239 }
1240
1241 /// Tests `Free::resume` on a `Bind` with `Wrap`.
1242 ///
1243 /// **What it tests:** Verifies that `resume` correctly handles bind chains that end in a `Wrap`.
1244 /// **How it tests:** Creates a `wrap(...).bind(...)` and checks that resume returns `Err(_)`.
1245 #[test]
1246 fn test_free_resume_bind_on_wrap() {
1247 let free = Free::<ThunkBrand, _>::wrap(Thunk::new(|| Free::pure(10)))
1248 .bind(|x: i32| Free::pure(x + 5));
1249 let result = free.resume();
1250 assert!(result.is_err());
1251 // The inner computation, when evaluated, should produce 15
1252 let thunk = result.unwrap_err();
1253 let inner_free: Free<ThunkBrand, i32> = thunk.evaluate();
1254 assert_eq!(inner_free.evaluate(), 15);
1255 }
1256
1257 /// A natural transformation from `ThunkBrand` to `OptionBrand`.
1258 ///
1259 /// Evaluates the thunk and wraps the result in `Some`.
1260 #[derive(Clone)]
1261 struct ThunkToOption;
1262 impl NaturalTransformation<ThunkBrand, OptionBrand> for ThunkToOption {
1263 fn transform<'a, A: 'a>(
1264 &self,
1265 fa: Thunk<'a, A>,
1266 ) -> Option<A> {
1267 Some(fa.evaluate())
1268 }
1269 }
1270
1271 /// Tests `Free::fold_free` on a `Pure` variant.
1272 ///
1273 /// **What it tests:** Verifies that `fold_free` on a pure value wraps it in the target monad.
1274 /// **How it tests:** Folds `Free::pure(42)` with `ThunkToOption` and asserts it returns `Some(42)`.
1275 #[test]
1276 fn test_free_fold_free_pure() {
1277 let free = Free::<ThunkBrand, _>::pure(42);
1278 let result: Option<i32> = free.fold_free::<OptionBrand>(ThunkToOption);
1279 assert_eq!(result, Some(42));
1280 }
1281
1282 /// Tests `Free::fold_free` on a suspended computation.
1283 ///
1284 /// **What it tests:** Verifies that `fold_free` correctly interprets a computation with effects.
1285 /// **How it tests:** Lifts a thunk into Free, then folds with `ThunkToOption`.
1286 #[test]
1287 fn test_free_fold_free_wrap() {
1288 let free = Free::<ThunkBrand, _>::lift_f(Thunk::new(|| 42));
1289 let result: Option<i32> = free.fold_free::<OptionBrand>(ThunkToOption);
1290 assert_eq!(result, Some(42));
1291 }
1292
1293 /// Tests `Free::fold_free` on a chained computation.
1294 ///
1295 /// **What it tests:** Verifies that `fold_free` correctly interprets a multi-step computation.
1296 /// **How it tests:** Chains several binds and folds with `ThunkToOption`.
1297 #[test]
1298 fn test_free_fold_free_chain() {
1299 let free = Free::<ThunkBrand, _>::lift_f(Thunk::new(|| 10))
1300 .bind(|x| Free::<ThunkBrand, _>::lift_f(Thunk::new(move || x * 2)))
1301 .bind(|x| Free::pure(x + 5));
1302 let result: Option<i32> = free.fold_free::<OptionBrand>(ThunkToOption);
1303 assert_eq!(result, Some(25));
1304 }
1305
1306 /// Tests `Free::fold_free` stack safety with a deeply nested `Wrap` chain.
1307 ///
1308 /// **What it tests:** Verifies that `fold_free` does not overflow the stack on deep structures.
1309 /// **How it tests:** Builds a chain of 100,000 `Wrap` layers and folds with `ThunkToOption`.
1310 #[test]
1311 fn test_free_fold_free_stack_safety() {
1312 let depth = 100_000;
1313 let mut free = Free::<ThunkBrand, i32>::pure(0);
1314 for _ in 0 .. depth {
1315 free = Free::wrap(Thunk::new(move || free));
1316 }
1317 let result: Option<i32> = free.fold_free::<OptionBrand>(ThunkToOption);
1318 assert_eq!(result, Some(0));
1319 }
1320
1321 /// Tests `Free::map` on a pure value.
1322 ///
1323 /// **What it tests:** Verifies that `map` correctly transforms a pure value.
1324 /// **How it tests:** Creates `Free::pure(10).map(|x| x * 2)` and evaluates.
1325 #[test]
1326 fn test_free_map_pure() {
1327 let free = Free::<ThunkBrand, _>::pure(10).map(|x| x * 2);
1328 assert_eq!(free.evaluate(), 20);
1329 }
1330
1331 /// Tests chained `Free::map` operations.
1332 ///
1333 /// **What it tests:** Verifies that multiple map operations compose correctly.
1334 /// **How it tests:** Chains three maps and verifies the final result.
1335 #[test]
1336 fn test_free_map_chain() {
1337 let free = Free::<ThunkBrand, _>::pure(5).map(|x| x + 1).map(|x| x * 3).map(|x| x - 2);
1338 assert_eq!(free.evaluate(), 16);
1339 }
1340
1341 /// Tests `Free::map` on a wrapped computation.
1342 ///
1343 /// **What it tests:** Verifies that map works on suspended computations.
1344 /// **How it tests:** Wraps a thunk in Free, maps over it, and evaluates.
1345 #[test]
1346 fn test_free_map_wrap() {
1347 let free = Free::<ThunkBrand, _>::lift_f(Thunk::new(|| 7)).map(|x| x * 3);
1348 assert_eq!(free.evaluate(), 21);
1349 }
1350
1351 /// Tests `Free::map` followed by `Free::bind`.
1352 ///
1353 /// **What it tests:** Verifies that map and bind interoperate correctly.
1354 /// **How it tests:** Maps, then binds, and checks the result.
1355 #[test]
1356 fn test_free_map_then_bind() {
1357 let free = Free::<ThunkBrand, _>::pure(10).map(|x| x + 5).bind(|x| Free::pure(x * 2));
1358 assert_eq!(free.evaluate(), 30);
1359 }
1360
1361 /// Tests `Free::bind` followed by `Free::map`.
1362 ///
1363 /// **What it tests:** Verifies that bind and map interoperate correctly.
1364 /// **How it tests:** Binds, then maps, and checks the result.
1365 #[test]
1366 fn test_free_bind_then_map() {
1367 let free = Free::<ThunkBrand, _>::pure(10).bind(|x| Free::pure(x + 5)).map(|x| x * 2);
1368 assert_eq!(free.evaluate(), 30);
1369 }
1370
1371 /// Tests stack safety of deep `Free::map` chains.
1372 ///
1373 /// **What it tests:** Verifies that deeply nested map chains do not overflow the stack.
1374 /// **How it tests:** Creates a chain of 10,000 map operations.
1375 #[test]
1376 fn test_free_map_deep_chain() {
1377 let mut free = Free::<ThunkBrand, _>::pure(0i64);
1378 for _ in 0 .. 10_000 {
1379 free = free.map(|x| x + 1);
1380 }
1381 assert_eq!(free.evaluate(), 10_000);
1382 }
1383
1384 // -- Monad law tests (Task 6.2h) --
1385
1386 /// Tests monad left identity law for `Free`.
1387 ///
1388 /// **What it tests:** Left identity: `pure(a).bind(f)` equals `f(a)`.
1389 /// **How it tests:** Applies `bind` to a `pure` value and compares the result
1390 /// to calling `f` directly on the same value.
1391 #[test]
1392 fn test_free_monad_left_identity() {
1393 let a = 42_i32;
1394 let f = |x: i32| Free::<ThunkBrand, _>::pure(x * 2 + 1);
1395
1396 let lhs = Free::<ThunkBrand, _>::pure(a).bind(f).evaluate();
1397 let rhs = f(a).evaluate();
1398 assert_eq!(lhs, rhs);
1399 }
1400
1401 /// Tests monad left identity law with `lift_f`.
1402 ///
1403 /// **What it tests:** Left identity with an effectful continuation: `pure(a).bind(f)`
1404 /// equals `f(a)` when `f` uses `lift_f`.
1405 /// **How it tests:** Uses `lift_f` inside the continuation to introduce a functor layer.
1406 #[test]
1407 fn test_free_monad_left_identity_with_lift_f() {
1408 let a = 10_i32;
1409 let f = |x: i32| Free::<ThunkBrand, _>::lift_f(Thunk::new(move || x + 5));
1410
1411 let lhs = Free::<ThunkBrand, _>::pure(a).bind(f).evaluate();
1412 let rhs = f(a).evaluate();
1413 assert_eq!(lhs, rhs);
1414 }
1415
1416 /// Tests monad right identity law for `Free`.
1417 ///
1418 /// **What it tests:** Right identity: `m.bind(pure)` equals `m`.
1419 /// **How it tests:** Binds `pure` to various `Free` computations and checks that
1420 /// the result is unchanged.
1421 #[test]
1422 fn test_free_monad_right_identity_pure() {
1423 let m = Free::<ThunkBrand, _>::pure(42);
1424 let result = m.bind(Free::pure).evaluate();
1425 assert_eq!(result, 42);
1426 }
1427
1428 /// Tests monad right identity law with a bind chain.
1429 ///
1430 /// **What it tests:** Right identity on a computation that already has binds:
1431 /// `m.bind(|x| Free::pure(x))` produces the same result as `m`.
1432 /// **How it tests:** Constructs a chain of binds and appends `pure` at the end.
1433 #[test]
1434 fn test_free_monad_right_identity_chain() {
1435 let m =
1436 Free::<ThunkBrand, _>::pure(10).bind(|x| Free::pure(x + 5)).bind(|x| Free::pure(x * 3));
1437
1438 let expected = Free::<ThunkBrand, _>::pure(10)
1439 .bind(|x| Free::pure(x + 5))
1440 .bind(|x| Free::pure(x * 3))
1441 .evaluate();
1442
1443 let result = m.bind(Free::pure).evaluate();
1444 assert_eq!(result, expected);
1445 }
1446
1447 /// Tests monad right identity law with a `Wrap` variant.
1448 ///
1449 /// **What it tests:** Right identity when the initial computation is a `Wrap`.
1450 /// **How it tests:** Wraps a thunk, binds `pure`, and checks the result.
1451 #[test]
1452 fn test_free_monad_right_identity_wrap() {
1453 let m = Free::<ThunkBrand, _>::wrap(Thunk::new(|| Free::pure(99)));
1454 let result = m.bind(Free::pure).evaluate();
1455 assert_eq!(result, 99);
1456 }
1457
1458 /// Tests monad associativity law for `Free`.
1459 ///
1460 /// **What it tests:** Associativity: `m.bind(f).bind(g)` equals
1461 /// `m.bind(|x| f(x).bind(g))`.
1462 /// **How it tests:** Evaluates both sides of the law and checks equality.
1463 #[test]
1464 fn test_free_monad_associativity() {
1465 let f = |x: i32| Free::<ThunkBrand, _>::pure(x + 10);
1466 let g = |x: i32| Free::<ThunkBrand, _>::pure(x * 2);
1467
1468 // Left grouping: (m >>= f) >>= g
1469 let lhs = Free::<ThunkBrand, _>::pure(5).bind(f).bind(g).evaluate();
1470
1471 // Right grouping: m >>= (\x -> f x >>= g)
1472 let rhs = Free::<ThunkBrand, _>::pure(5).bind(move |x| f(x).bind(g)).evaluate();
1473
1474 assert_eq!(lhs, rhs);
1475 assert_eq!(lhs, 30); // (5 + 10) * 2
1476 }
1477
1478 /// Tests monad associativity law with effectful continuations.
1479 ///
1480 /// **What it tests:** Associativity when continuations use `lift_f` for real effects.
1481 /// **How it tests:** Both groupings produce the same result when `lift_f` is involved.
1482 #[test]
1483 fn test_free_monad_associativity_with_effects() {
1484 let f = |x: i32| Free::<ThunkBrand, _>::lift_f(Thunk::new(move || x + 3));
1485 let g = |x: i32| Free::<ThunkBrand, _>::lift_f(Thunk::new(move || x * 4));
1486
1487 let lhs = Free::<ThunkBrand, _>::pure(7).bind(f).bind(g).evaluate();
1488 let rhs = Free::<ThunkBrand, _>::pure(7).bind(move |x| f(x).bind(g)).evaluate();
1489
1490 assert_eq!(lhs, rhs);
1491 assert_eq!(lhs, 40); // (7 + 3) * 4
1492 }
1493
1494 /// Tests monad associativity law with three continuations.
1495 ///
1496 /// **What it tests:** Associativity holds for longer chains where multiple
1497 /// groupings are possible.
1498 /// **How it tests:** Compares sequential bind with nested bind for three functions.
1499 #[test]
1500 fn test_free_monad_associativity_three_functions() {
1501 let f = |x: i32| Free::<ThunkBrand, _>::pure(x + 1);
1502 let g = |x: i32| Free::<ThunkBrand, _>::pure(x * 2);
1503 let h = |x: i32| Free::<ThunkBrand, _>::pure(x - 3);
1504
1505 // ((m >>= f) >>= g) >>= h
1506 let lhs = Free::<ThunkBrand, _>::pure(10).bind(f).bind(g).bind(h).evaluate();
1507
1508 // m >>= (\x -> f(x) >>= (\y -> g(y) >>= h))
1509 let rhs = Free::<ThunkBrand, _>::pure(10)
1510 .bind(move |x| f(x).bind(move |y| g(y).bind(h)))
1511 .evaluate();
1512
1513 assert_eq!(lhs, rhs);
1514 assert_eq!(lhs, 19); // ((10 + 1) * 2) - 3
1515 }
1516
1517 // -- Mixed deep chain tests (Task 6.2i) --
1518
1519 /// Tests interleaved `bind` and `wrap` in a deep chain.
1520 ///
1521 /// **What it tests:** Verifies that alternating `bind` and `wrap` operations
1522 /// compose correctly and evaluate to the expected result.
1523 /// **How it tests:** Builds a chain that alternates between `bind` (increment)
1524 /// and `wrap` (deferred computation) for 1,000 iterations.
1525 #[test]
1526 fn test_free_mixed_bind_and_wrap() {
1527 let mut free = Free::<ThunkBrand, _>::pure(0_i32);
1528 for _ in 0 .. 1_000 {
1529 free = free.bind(|x| {
1530 let inner = Free::pure(x + 1);
1531 Free::wrap(Thunk::new(move || inner))
1532 });
1533 }
1534 assert_eq!(free.evaluate(), 1_000);
1535 }
1536
1537 /// Tests interleaved `bind` and `lift_f` in a deep chain.
1538 ///
1539 /// **What it tests:** Verifies that alternating `bind` and `lift_f` operations
1540 /// compose correctly.
1541 /// **How it tests:** Builds a chain of 1,000 iterations mixing `bind` with `lift_f`.
1542 #[test]
1543 fn test_free_mixed_bind_and_lift_f() {
1544 let mut free = Free::<ThunkBrand, _>::pure(0_i32);
1545 for i in 0 .. 1_000 {
1546 if i % 2 == 0 {
1547 free = free.bind(|x| Free::pure(x + 1));
1548 } else {
1549 free = free.bind(|x| Free::<ThunkBrand, _>::lift_f(Thunk::new(move || x + 1)));
1550 }
1551 }
1552 assert_eq!(free.evaluate(), 1_000);
1553 }
1554
1555 /// Tests a deep chain mixing all four construction methods.
1556 ///
1557 /// **What it tests:** Verifies that `pure`, `bind`, `wrap`, and `lift_f` can be
1558 /// freely interleaved in a single computation chain.
1559 /// **How it tests:** Applies a rotating pattern of operations across 400 iterations
1560 /// (100 of each type) and checks the final accumulated value.
1561 #[test]
1562 fn test_free_mixed_all_constructors() {
1563 let mut free = Free::<ThunkBrand, _>::pure(0_i32);
1564 for i in 0 .. 400 {
1565 match i % 4 {
1566 0 => {
1567 // bind with pure
1568 free = free.bind(|x| Free::pure(x + 1));
1569 }
1570 1 => {
1571 // bind with lift_f
1572 free = free.bind(|x| Free::<ThunkBrand, _>::lift_f(Thunk::new(move || x + 1)));
1573 }
1574 2 => {
1575 // bind with wrap
1576 free = free.bind(|x| {
1577 let inner = Free::pure(x + 1);
1578 Free::wrap(Thunk::new(move || inner))
1579 });
1580 }
1581 3 => {
1582 // nested bind
1583 free =
1584 free.bind(|x| Free::<ThunkBrand, _>::pure(x).bind(|y| Free::pure(y + 1)));
1585 }
1586 _ => unreachable!(),
1587 }
1588 }
1589 assert_eq!(free.evaluate(), 400);
1590 }
1591
1592 /// Tests deep chain with `lift_f` at the root.
1593 ///
1594 /// **What it tests:** Verifies that starting a chain from `lift_f` (a `Wrap` variant)
1595 /// and then applying many `bind` operations works correctly.
1596 /// **How it tests:** Starts with `lift_f` and chains 10,000 binds.
1597 #[test]
1598 fn test_free_deep_chain_from_lift_f() {
1599 let mut free = Free::<ThunkBrand, _>::lift_f(Thunk::new(|| 0_i32));
1600 for _ in 0 .. 10_000 {
1601 free = free.bind(|x| Free::pure(x + 1));
1602 }
1603 assert_eq!(free.evaluate(), 10_000);
1604 }
1605
1606 /// Tests drop safety of deep `Free::map` chains.
1607 ///
1608 /// **What it tests:** Verifies that dropping deeply nested map chains does not overflow the stack.
1609 /// **How it tests:** Creates a deep map chain and drops it without evaluating.
1610 #[test]
1611 fn test_free_map_drop_safety() {
1612 let mut free = Free::<ThunkBrand, _>::pure(0i64);
1613 for _ in 0 .. 10_000 {
1614 free = free.map(|x| x + 1);
1615 }
1616 drop(free);
1617 }
1618
1619 /// Tests deep chain with nested wraps.
1620 ///
1621 /// **What it tests:** Verifies that deeply nested `wrap` operations evaluate correctly.
1622 /// **How it tests:** Creates 1,000 nested wraps around a pure value.
1623 #[test]
1624 fn test_free_deep_nested_wraps() {
1625 let mut free = Free::<ThunkBrand, _>::pure(42_i32);
1626 for _ in 0 .. 1_000 {
1627 let inner = free;
1628 free = Free::wrap(Thunk::new(move || inner));
1629 }
1630 assert_eq!(free.evaluate(), 42);
1631 }
1632
1633 /// Tests dropping a deeply mixed chain without evaluating.
1634 ///
1635 /// **What it tests:** Verifies that Drop handles deep chains with interleaved
1636 /// `bind`, `wrap`, and `lift_f` without stack overflow.
1637 /// **How it tests:** Builds a mixed chain of 50,000 operations and drops it.
1638 #[test]
1639 fn test_free_drop_deep_mixed_chain() {
1640 let mut free = Free::<ThunkBrand, _>::pure(0_i32);
1641 for i in 0 .. 50_000 {
1642 if i % 3 == 0 {
1643 free = free.bind(|x| Free::pure(x + 1));
1644 } else if i % 3 == 1 {
1645 free = free.bind(|x| Free::<ThunkBrand, _>::lift_f(Thunk::new(move || x + 1)));
1646 } else {
1647 free = free.bind(|x| {
1648 let inner = Free::pure(x + 1);
1649 Free::wrap(Thunk::new(move || inner))
1650 });
1651 }
1652 }
1653 // Drop without evaluating; should not overflow the stack.
1654
1655 drop(free);
1656 }
1657
1658 // -- hoist_free tests --
1659
1660 /// An identity natural transformation from `ThunkBrand` to `ThunkBrand`.
1661 ///
1662 /// Used to test `hoist_free` with the same source and target functor.
1663 #[derive(Clone)]
1664 struct ThunkId;
1665 impl NaturalTransformation<ThunkBrand, ThunkBrand> for ThunkId {
1666 fn transform<'a, A: 'a>(
1667 &self,
1668 fa: Thunk<'a, A>,
1669 ) -> Thunk<'a, A> {
1670 fa
1671 }
1672 }
1673
1674 /// A natural transformation that wraps a thunk in an extra layer.
1675 ///
1676 /// Evaluates the original thunk eagerly and returns a new thunk producing
1677 /// that value, demonstrating the transformation is actually invoked.
1678 #[derive(Clone)]
1679 struct ThunkEager;
1680 impl NaturalTransformation<ThunkBrand, ThunkBrand> for ThunkEager {
1681 fn transform<'a, A: 'a>(
1682 &self,
1683 fa: Thunk<'a, A>,
1684 ) -> Thunk<'a, A> {
1685 let val = fa.evaluate();
1686 Thunk::new(move || val)
1687 }
1688 }
1689
1690 /// Tests `Free::hoist_free` on a pure value.
1691 ///
1692 /// **What it tests:** Verifies that hoisting a pure computation preserves the value,
1693 /// since there are no `Wrap` layers for the natural transformation to act on.
1694 /// **How it tests:** Creates `Free::pure(42)`, hoists with `ThunkId`, evaluates.
1695 #[test]
1696 fn test_free_hoist_free_pure() {
1697 let free = Free::<ThunkBrand, _>::pure(42);
1698 let hoisted = free.hoist_free(ThunkId);
1699 assert_eq!(hoisted.evaluate(), 42);
1700 }
1701
1702 /// Tests `Free::hoist_free` on a single `Wrap` layer.
1703 ///
1704 /// **What it tests:** Verifies that the natural transformation is applied to the functor layer.
1705 /// **How it tests:** Lifts a thunk into Free, hoists with `ThunkId`, and evaluates.
1706 #[test]
1707 fn test_free_hoist_free_single_wrap() {
1708 let free = Free::<ThunkBrand, _>::lift_f(Thunk::new(|| 42));
1709 let hoisted = free.hoist_free(ThunkId);
1710 assert_eq!(hoisted.evaluate(), 42);
1711 }
1712
1713 /// Tests `Free::hoist_free` with a transformation that eagerly evaluates.
1714 ///
1715 /// **What it tests:** Verifies the natural transformation is actually invoked on each layer.
1716 /// **How it tests:** Uses `ThunkEager` which evaluates the thunk during transformation,
1717 /// proving the transformation runs rather than being a no-op.
1718 #[test]
1719 fn test_free_hoist_free_transformation_applied() {
1720 let free = Free::<ThunkBrand, _>::lift_f(Thunk::new(|| 42));
1721 let hoisted = free.hoist_free(ThunkEager);
1722 assert_eq!(hoisted.evaluate(), 42);
1723 }
1724
1725 /// Tests `Free::hoist_free` with multiple `Wrap` layers.
1726 ///
1727 /// **What it tests:** Verifies that each functor layer is transformed.
1728 /// **How it tests:** Builds a chain of three nested `Wrap` layers and hoists all of them.
1729 #[test]
1730 fn test_free_hoist_free_multiple_wraps() {
1731 let free = Free::<ThunkBrand, _>::wrap(Thunk::new(|| {
1732 Free::wrap(Thunk::new(|| Free::wrap(Thunk::new(|| Free::pure(7)))))
1733 }));
1734 let hoisted = free.hoist_free(ThunkId);
1735 assert_eq!(hoisted.evaluate(), 7);
1736 }
1737
1738 /// Tests `Free::hoist_free` with bind chains.
1739 ///
1740 /// **What it tests:** Verifies that bind chains are preserved through hoisting.
1741 /// **How it tests:** Chains `lift_f` and `bind`, hoists, and checks the final result.
1742 #[test]
1743 fn test_free_hoist_free_with_binds() {
1744 let free = Free::<ThunkBrand, _>::lift_f(Thunk::new(|| 10))
1745 .bind(|x| Free::<ThunkBrand, _>::lift_f(Thunk::new(move || x * 2)))
1746 .bind(|x| Free::pure(x + 5));
1747 let hoisted = free.hoist_free(ThunkId);
1748 assert_eq!(hoisted.evaluate(), 25);
1749 }
1750
1751 /// Tests `Free::hoist_free` preserves values across a deep wrap chain.
1752 ///
1753 /// **What it tests:** Verifies hoisting works on moderately deep wrap chains.
1754 /// **How it tests:** Builds 100 nested `Wrap` layers and hoists them all.
1755 #[test]
1756 fn test_free_hoist_free_deep_wraps() {
1757 let depth = 100;
1758 let mut free = Free::<ThunkBrand, i32>::pure(99);
1759 for _ in 0 .. depth {
1760 free = Free::wrap(Thunk::new(move || free));
1761 }
1762 let hoisted = free.hoist_free(ThunkId);
1763 assert_eq!(hoisted.evaluate(), 99);
1764 }
1765
1766 /// Tests `Free::hoist_free` is stack-safe with 100k nested `Wrap` layers.
1767 ///
1768 /// **What it tests:** Verifies that `hoist_free` does not overflow the stack on deeply
1769 /// nested `Suspend` chains. The old recursive implementation would overflow at this depth.
1770 /// **How it tests:** Builds 100,000 nested `Wrap` layers, hoists with `ThunkId`, and evaluates.
1771 #[test]
1772 fn test_free_hoist_free_stack_safety() {
1773 let depth = 100_000;
1774 let mut free = Free::<ThunkBrand, i32>::pure(42);
1775 for _ in 0 .. depth {
1776 free = Free::wrap(Thunk::new(move || free));
1777 }
1778 let hoisted = free.hoist_free(ThunkId);
1779 assert_eq!(hoisted.evaluate(), 42);
1780 }
1781
1782 // -- to_view tests --
1783
1784 /// Tests `Free::to_view` on a pure value.
1785 ///
1786 /// **What it tests:** Verifies that `to_view` on a pure computation returns `FreeStep::Done`.
1787 /// **How it tests:** Creates `Free::pure(42)` and checks that `to_view` produces `Done(42)`.
1788 #[test]
1789 fn test_free_to_view_pure() {
1790 let free = Free::<ThunkBrand, _>::pure(42);
1791 match free.to_view() {
1792 FreeStep::Done(a) => assert_eq!(a, 42),
1793 FreeStep::Suspended(_) => panic!("expected Done"),
1794 }
1795 }
1796
1797 /// Tests `Free::to_view` on a wrapped value.
1798 ///
1799 /// **What it tests:** Verifies that `to_view` on a suspended computation returns
1800 /// `FreeStep::Suspended`.
1801 /// **How it tests:** Wraps a `Free::pure(99)` in a `Thunk` and checks that `to_view`
1802 /// produces `Suspended`, then evaluates the inner layer to verify the value.
1803 #[test]
1804 fn test_free_to_view_wrapped() {
1805 let free = Free::<ThunkBrand, _>::wrap(Thunk::new(|| Free::pure(99)));
1806 match free.to_view() {
1807 FreeStep::Done(_) => panic!("expected Suspended"),
1808 FreeStep::Suspended(thunk) => {
1809 let inner: Free<ThunkBrand, i32> = thunk.evaluate();
1810 assert_eq!(inner.evaluate(), 99);
1811 }
1812 }
1813 }
1814
1815 /// Tests `Free::to_view` on a bind chain that resolves to a pure value.
1816 ///
1817 /// **What it tests:** Verifies that `to_view` collapses a chain of `bind` operations
1818 /// and returns `Done` when all continuations produce pure values.
1819 /// **How it tests:** Creates `pure(10).bind(|x| pure(x + 5))` and checks that `to_view`
1820 /// returns `Done(15)`.
1821 #[test]
1822 fn test_free_to_view_bind_chain_done() {
1823 let free = Free::<ThunkBrand, _>::pure(10).bind(|x: i32| Free::pure(x + 5));
1824 match free.to_view() {
1825 FreeStep::Done(a) => assert_eq!(a, 15),
1826 FreeStep::Suspended(_) => panic!("expected Done"),
1827 }
1828 }
1829
1830 /// Tests `Free::to_view` on a bind chain that resolves to a suspended layer.
1831 ///
1832 /// **What it tests:** Verifies that `to_view` collapses bind continuations until it
1833 /// reaches a `Suspend`, then returns `Suspended` with continuations reattached.
1834 /// **How it tests:** Creates `wrap(...).bind(|x| pure(x + 5))` and checks that `to_view`
1835 /// returns `Suspended`, then evaluates the result to verify the value is correct.
1836 #[test]
1837 fn test_free_to_view_bind_chain_suspended() {
1838 let free = Free::<ThunkBrand, _>::wrap(Thunk::new(|| Free::pure(10)))
1839 .bind(|x: i32| Free::pure(x + 5));
1840 match free.to_view() {
1841 FreeStep::Done(_) => panic!("expected Suspended"),
1842 FreeStep::Suspended(thunk) => {
1843 // The inner Free should have the bind continuation reattached,
1844 // so evaluating the full thing should yield 15.
1845 let inner: Free<ThunkBrand, i32> = thunk.evaluate();
1846 assert_eq!(inner.evaluate(), 15);
1847 }
1848 }
1849 }
1850
1851 /// Tests that `resume` delegates correctly to `to_view` after refactoring.
1852 ///
1853 /// **What it tests:** Verifies that `resume` still produces the same results as before
1854 /// the refactoring to use `to_view`.
1855 /// **How it tests:** Exercises both `Ok` and `Err` paths and verifies values.
1856 #[test]
1857 fn test_free_resume_delegates_to_view() {
1858 // Pure -> Ok
1859 let free = Free::<ThunkBrand, _>::pure(42);
1860 assert!(matches!(free.resume(), Ok(42)));
1861
1862 // Wrap -> Err
1863 let free = Free::<ThunkBrand, _>::wrap(Thunk::new(|| Free::pure(99)));
1864 let err = free.resume().unwrap_err();
1865 let inner: Free<ThunkBrand, i32> = err.evaluate();
1866 assert_eq!(inner.evaluate(), 99);
1867
1868 // Bind chain -> Ok
1869 let free = Free::<ThunkBrand, _>::pure(10).bind(|x: i32| Free::pure(x + 5));
1870 assert!(matches!(free.resume(), Ok(15)));
1871 }
1872
1873 /// Tests that `evaluate` delegates correctly to `to_view` after refactoring.
1874 ///
1875 /// **What it tests:** Verifies that `evaluate` still produces the same results as before
1876 /// the refactoring to use `to_view`.
1877 /// **How it tests:** Exercises pure, wrapped, and bind-chain computations.
1878 #[test]
1879 fn test_free_evaluate_delegates_to_view() {
1880 assert_eq!(Free::<ThunkBrand, _>::pure(42).evaluate(), 42);
1881
1882 let free = Free::<ThunkBrand, _>::wrap(Thunk::new(|| Free::pure(99)));
1883 assert_eq!(free.evaluate(), 99);
1884
1885 let free =
1886 Free::<ThunkBrand, _>::pure(10).bind(|x| Free::pure(x + 5)).bind(|x| Free::pure(x * 2));
1887 assert_eq!(free.evaluate(), 30);
1888 }
1889
1890 // -- substitute_free tests --
1891
1892 /// Tests `substitute_free` with an identity-like substitution.
1893 ///
1894 /// **What it tests:** Verifies that substituting each `F` layer with `lift_f` (wrapping it
1895 /// back into `Free<F, _>`) preserves the computation result.
1896 /// **How it tests:** Lifts a thunk into Free, substitutes with `lift_f`, and evaluates.
1897 #[test]
1898 fn test_free_substitute_free_identity() {
1899 let free = Free::<ThunkBrand, _>::lift_f(Thunk::new(|| 42));
1900 let substituted = free.substitute_free(|thunk: Thunk<'static, Free<ThunkBrand, i32>>| {
1901 Free::<ThunkBrand, _>::lift_f(thunk)
1902 });
1903 assert_eq!(substituted.evaluate(), 42);
1904 }
1905
1906 /// Tests `substitute_free` on a pure value.
1907 ///
1908 /// **What it tests:** Verifies that `substitute_free` on a pure computation returns
1909 /// the same value without invoking the natural transformation.
1910 /// **How it tests:** Creates `Free::pure(42)` and substitutes, checking the result.
1911 #[test]
1912 fn test_free_substitute_free_pure() {
1913 let free = Free::<ThunkBrand, _>::pure(42);
1914 let substituted: Free<ThunkBrand, i32> =
1915 free.substitute_free(|_thunk: Thunk<'static, Free<ThunkBrand, i32>>| {
1916 panic!("should not be called on a pure value")
1917 });
1918 assert_eq!(substituted.evaluate(), 42);
1919 }
1920
1921 /// Tests `substitute_free` with a chain of binds.
1922 ///
1923 /// **What it tests:** Verifies that `substitute_free` correctly handles computations
1924 /// with bind chains.
1925 /// **How it tests:** Creates a computation with `lift_f` and `bind`, substitutes, and
1926 /// checks the final result.
1927 #[test]
1928 fn test_free_substitute_free_with_binds() {
1929 let free = Free::<ThunkBrand, _>::lift_f(Thunk::new(|| 10))
1930 .bind(|x| Free::<ThunkBrand, _>::lift_f(Thunk::new(move || x * 2)))
1931 .bind(|x| Free::pure(x + 5));
1932 let substituted: Free<ThunkBrand, i32> =
1933 free.substitute_free(|thunk: Thunk<'static, Free<ThunkBrand, i32>>| {
1934 Free::<ThunkBrand, _>::lift_f(thunk)
1935 });
1936 assert_eq!(substituted.evaluate(), 25);
1937 }
1938
1939 /// Tests `substitute_free` that expands each layer into multiple layers.
1940 ///
1941 /// **What it tests:** Verifies that `substitute_free` can expand each `F` layer into
1942 /// an entire `Free<G, _>` sub-computation (not just a single layer).
1943 /// **How it tests:** Substitutes each thunk layer with a double-wrapped computation
1944 /// and verifies the final result is correct.
1945 #[test]
1946 fn test_free_substitute_free_expands_layers() {
1947 let free = Free::<ThunkBrand, _>::lift_f(Thunk::new(|| 7));
1948 // Each thunk layer becomes a double-wrapped Free
1949 let substituted: Free<ThunkBrand, i32> =
1950 free.substitute_free(|thunk: Thunk<'static, Free<ThunkBrand, i32>>| {
1951 // Wrap the original thunk layer, then add an extra wrap layer
1952 let inner = Free::<ThunkBrand, _>::lift_f(thunk);
1953 Free::wrap(Thunk::new(move || inner))
1954 });
1955 assert_eq!(substituted.evaluate(), 7);
1956 }
1957}