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}