Skip to main content

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}