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