Skip to main content

fp_library/types/
free.rs

1use crate::{
2	Apply,
3	classes::{Functor, Runnable},
4	kinds::*,
5	types::cat_list::CatList,
6};
7use std::{any::Any, marker::PhantomData};
8
9/// A type-erased value for internal use.
10///
11/// This type alias represents a value whose type has been erased to `Box<dyn Any>`.
12/// It is used within the internal implementation of `Free` to allow for
13/// heterogeneous chains of computations in the [`CatList`].
14type Val = Box<dyn Any>;
15
16/// A type-erased continuation.
17///
18/// This type alias represents a function that takes a type-erased value [`Val`]
19/// and returns a new `Free` computation (also type-erased).
20///
21/// ### Type Parameters
22///
23/// * `F`: The base functor.
24type Cont<F> = Box<dyn FnOnce(Val) -> Free<F, Val>>;
25
26/// The internal representation of the `Free` monad.
27///
28/// This enum encodes the structure of the free monad, supporting
29/// pure values, suspended computations, and efficient concatenation of binds.
30///
31/// ### Type Parameters
32///
33/// * `F`: The base functor (must implement [`Functor`]).
34/// * `A`: The result type.
35///
36enum FreeInner<F, A>
37where
38	F: Functor + 'static,
39	A: 'static,
40{
41	/// A pure value.
42	///
43	/// This variant represents a computation that has finished and produced a value.
44	Pure(A),
45
46	/// A suspended computation.
47	///
48	/// This variant represents a computation that is suspended in the functor `F`.
49	/// The functor contains the next step of the computation.
50	Roll(Apply!(<F as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'static, Free<F, A>>)),
51
52	/// A bind operation.
53	///
54	/// This variant represents a computation followed by a sequence of continuations.
55	/// It uses a [`CatList`] to store continuations, ensuring O(1) append complexity
56	/// for left-associated binds.
57	///
58	/// ### Fields
59	///
60	/// * `head`: The initial computation.
61	/// * `conts`: The list of continuations to apply to the result of `head`.
62	Bind { head: Box<Free<F, Val>>, conts: CatList<Cont<F>>, _marker: PhantomData<A> },
63}
64
65/// The Free monad with O(1) bind via CatList.
66///
67/// This implementation follows "Reflection without Remorse" to ensure
68/// that left-associated binds do not degrade performance.
69///
70/// # HKT and Lifetime Limitations
71///
72/// `Free` does not implement HKT traits (like `Functor`, `Monad`) from this library.
73///
74/// ## The Conflict
75/// * **The Traits**: The `Kind` trait implemented by the `Functor` hierarchy requires the type
76///   constructor to accept *any* lifetime `'a` (e.g., `type Of<'a, A> = Free<F, A>`).
77/// * **The Implementation**: This implementation uses `Box<dyn Any>` to type-erase continuations
78///   for the "Reflection without Remorse" optimization. `dyn Any` strictly requires `A: 'static`.
79///
80/// This creates an unresolvable conflict: `Free` cannot support non-static references (like `&'a str`),
81/// so it cannot satisfy the `Kind` signature.
82///
83/// ## Why not use the "Naive" Recursive Definition?
84///
85/// A naive definition (`enum Free { Pure(A), Roll(F<Box<Free<F, A>>>) }`) would support lifetimes
86/// and HKT traits. However, it was rejected because:
87/// 1.  **Stack Safety**: `run` would not be stack-safe for deep computations.
88/// 2.  **Performance**: `bind` would be O(N), leading to quadratic complexity for sequences of binds.
89///
90/// This implementation prioritizes **stack safety** and **O(1) bind** over HKT trait compatibility.
91///
92/// ### Type Parameters
93///
94/// * `F`: The base functor (must implement [`Functor`]).
95/// * `A`: The result type.
96///
97/// ### Examples
98///
99/// ```
100/// use fp_library::{brands::*, types::*};
101///
102/// let free = Free::<ThunkBrand, _>::pure(42);
103/// ```
104pub struct Free<F, A>(Option<FreeInner<F, A>>)
105where
106	F: Functor + 'static,
107	A: 'static;
108
109impl<F, A> Free<F, A>
110where
111	F: Functor + 'static,
112	A: 'static,
113{
114	/// Creates a pure Free value.
115	///
116	/// ### Type Signature
117	///
118	/// `forall f a. a -> Free f a`
119	///
120	/// ### Parameters
121	///
122	/// * `a`: The value to wrap.
123	///
124	/// ### Returns
125	///
126	/// A `Free` computation that produces `a`.
127	///
128	/// ### Examples
129	///
130	/// ```
131	/// use fp_library::{brands::*, types::*};
132	///
133	/// let free = Free::<ThunkBrand, _>::pure(42);
134	/// ```
135	#[inline]
136	pub fn pure(a: A) -> Self {
137		Free(Some(FreeInner::Pure(a)))
138	}
139
140	/// Creates a suspended computation from a functor value.
141	///
142	/// ### Type Signature
143	///
144	/// `forall f a. f (Free f a) -> Free f a`
145	///
146	/// ### Parameters
147	///
148	/// * `fa`: The functor value containing the next step.
149	///
150	/// ### Returns
151	///
152	/// A `Free` computation that performs the effect `fa`.
153	///
154	/// ### Examples
155	///
156	/// ```
157	/// use fp_library::{brands::*, types::*};
158	///
159	/// let eval = Thunk::new(|| Free::pure(42));
160	/// let free = Free::<ThunkBrand, _>::roll(eval);
161	/// ```
162	pub fn roll(
163		fa: Apply!(<F as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'static, Free<F, A>>)
164	) -> Self {
165		Free(Some(FreeInner::Roll(fa)))
166	}
167
168	/// Monadic bind (flatMap) with O(1) complexity.
169	///
170	/// ### Type Signature
171	///
172	/// `forall f b a. (a -> Free f b, Free f a) -> Free f b`
173	///
174	/// ### Type Parameters
175	///
176	/// * `B`: The result type of the new computation.
177	///
178	/// ### Parameters
179	///
180	/// * `f`: The function to apply to the result of this computation.
181	///
182	/// ### Returns
183	///
184	/// A new `Free` computation that chains `f` after this computation.
185	///
186	/// ### Examples
187	///
188	/// ```
189	/// use fp_library::{brands::*, types::*};
190	///
191	/// let free = Free::<ThunkBrand, _>::pure(42)
192	///     .bind(|x| Free::pure(x + 1));
193	/// ```
194	pub fn bind<B: 'static>(
195		mut self,
196		f: impl FnOnce(A) -> Free<F, B> + 'static,
197	) -> Free<F, B> {
198		// Type-erase the continuation
199		let erased_f: Cont<F> = Box::new(move |val: Val| {
200			let a: A = *val.downcast().expect("Type mismatch in Free::bind");
201			let free_b: Free<F, B> = f(a);
202			free_b.erase_type()
203		});
204
205		// Extract inner safely
206		let inner = self.0.take().expect("Free value already consumed");
207
208		match inner {
209			// Pure: create a Bind with this continuation
210			FreeInner::Pure(a) => {
211				let head: Free<F, Val> = Free::pure(a).erase_type();
212				Free(Some(FreeInner::Bind {
213					head: Box::new(head),
214					conts: CatList::singleton(erased_f),
215					_marker: PhantomData,
216				}))
217			}
218
219			// Roll: wrap in a Bind
220			FreeInner::Roll(fa) => {
221				let head = Free::roll(fa).erase_type_boxed();
222				Free(Some(FreeInner::Bind {
223					head,
224					conts: CatList::singleton(erased_f),
225					_marker: PhantomData,
226				}))
227			}
228
229			// Bind: snoc the new continuation onto the CatList (O(1)!)
230			FreeInner::Bind { head, conts, .. } => Free(Some(FreeInner::Bind {
231				head,
232				conts: conts.snoc(erased_f),
233				_marker: PhantomData,
234			})),
235		}
236	}
237
238	/// Converts to type-erased form.
239	fn erase_type(mut self) -> Free<F, Val> {
240		let inner = self.0.take().expect("Free value already consumed");
241
242		match inner {
243			FreeInner::Pure(a) => Free(Some(FreeInner::Pure(Box::new(a) as Val))),
244			FreeInner::Roll(fa) => {
245				// Map over the functor to erase the inner type
246				let erased = F::map(|inner: Free<F, A>| inner.erase_type(), fa);
247				Free(Some(FreeInner::Roll(erased)))
248			}
249			FreeInner::Bind { head, conts, .. } => {
250				Free(Some(FreeInner::Bind { head, conts, _marker: PhantomData }))
251			}
252		}
253	}
254
255	/// Converts to boxed type-erased form.
256	fn erase_type_boxed(self) -> Box<Free<F, Val>> {
257		Box::new(self.erase_type())
258	}
259
260	/// Executes the Free computation, returning the final result.
261	///
262	/// This is the "trampoline" that iteratively processes the
263	/// CatList of continuations without growing the stack.
264	///
265	/// ### Type Signature
266	///
267	/// `forall f a. Runnable f => Free f a -> a`
268	///
269	/// ### Returns
270	///
271	/// The final result of the computation.
272	///
273	/// ### Examples
274	///
275	/// ```
276	/// use fp_library::{brands::*, types::*};
277	///
278	/// let free = Free::<ThunkBrand, _>::pure(42);
279	/// assert_eq!(free.run(), 42);
280	/// ```
281	pub fn run(self) -> A
282	where
283		F: Runnable,
284	{
285		// Start with a type-erased version
286		let mut current: Free<F, Val> = self.erase_type();
287		let mut conts: CatList<Cont<F>> = CatList::empty();
288
289		loop {
290			let inner = current.0.take().expect("Free value already consumed");
291
292			match inner {
293				FreeInner::Pure(val) => {
294					// Try to apply the next continuation
295					match conts.uncons() {
296						Some((cont, rest)) => {
297							current = cont(val);
298							conts = rest;
299						}
300						None => {
301							// No more continuations - we're done!
302							return *val
303								.downcast::<A>()
304								.expect("Type mismatch in Free::run final downcast");
305						}
306					}
307				}
308
309				FreeInner::Roll(fa) => {
310					// Run the effect to get the inner Free
311					current = <F as Runnable>::run(fa);
312				}
313
314				FreeInner::Bind { head, conts: inner_conts, .. } => {
315					// Merge the inner continuations with outer ones
316					// This is where CatList's O(1) append shines!
317					current = *head;
318					conts = inner_conts.append(conts);
319				}
320			}
321		}
322	}
323}
324
325impl<F, A> Drop for Free<F, A>
326where
327	F: Functor + 'static,
328	A: 'static,
329{
330	fn drop(&mut self) {
331		// We take the inner value out.
332		let inner = self.0.take();
333
334		// If the top level is a Bind, we need to start the iterative drop chain.
335		if let Some(FreeInner::Bind { mut head, .. }) = inner {
336			// head is Box<Free<F, Val>>.
337			// We take its inner value to continue the chain.
338			// From now on, everything is typed as FreeInner<F, Val>.
339			let mut current = head.0.take();
340
341			while let Some(FreeInner::Bind { mut head, .. }) = current {
342				current = head.0.take();
343			}
344		}
345	}
346}
347
348#[cfg(test)]
349mod tests {
350	use super::*;
351	use crate::{brands::ThunkBrand, types::thunk::Thunk};
352
353	/// Tests `Free::pure`.
354	///
355	/// **What it tests:** Verifies that `pure` creates a computation that simply returns the provided value.
356	/// **How it tests:** Constructs a `Free::pure(42)` and runs it, asserting the result is 42.
357	#[test]
358	fn test_free_pure() {
359		let free = Free::<ThunkBrand, _>::pure(42);
360		assert_eq!(free.run(), 42);
361	}
362
363	/// Tests `Free::roll`.
364	///
365	/// **What it tests:** Verifies that `roll` creates a computation from a suspended effect.
366	/// **How it tests:** Wraps a `Free::pure(42)` inside a `Thunk`, rolls it into a `Free`, and runs it to ensure it unwraps correctly.
367	#[test]
368	fn test_free_roll() {
369		let eval = Thunk::new(|| Free::pure(42));
370		let free = Free::<ThunkBrand, _>::roll(eval);
371		assert_eq!(free.run(), 42);
372	}
373
374	/// Tests `Free::bind`.
375	///
376	/// **What it tests:** Verifies that `bind` correctly chains computations and passes values between them.
377	/// **How it tests:** Chains `pure(42) -> bind(+1) -> bind(*2)` and asserts the result is (42+1)*2 = 86.
378	#[test]
379	fn test_free_bind() {
380		let free =
381			Free::<ThunkBrand, _>::pure(42).bind(|x| Free::pure(x + 1)).bind(|x| Free::pure(x * 2));
382		assert_eq!(free.run(), 86);
383	}
384
385	/// Tests stack safety of `Free::run`.
386	///
387	/// **What it tests:** Verifies that `run` can handle deep recursion without stack overflow (trampolining).
388	/// **How it tests:** Creates a recursive `count_down` function that builds a chain of 100,000 `bind` calls.
389	/// If the implementation were not stack-safe, this would crash with a stack overflow.
390	#[test]
391	fn test_free_stack_safety() {
392		fn count_down(n: i32) -> Free<ThunkBrand, i32> {
393			if n == 0 { Free::pure(0) } else { Free::pure(n).bind(|n| count_down(n - 1)) }
394		}
395
396		// 100,000 iterations should overflow stack if not safe
397		let free = count_down(100_000);
398		assert_eq!(free.run(), 0);
399	}
400
401	/// Tests stack safety of `Free::drop`.
402	///
403	/// **What it tests:** Verifies that dropping a deep `Free` computation does not cause a stack overflow.
404	/// **How it tests:** Constructs a deep `Free` chain (similar to `test_free_stack_safety`) and lets it go out of scope.
405	#[test]
406	fn test_free_drop_safety() {
407		fn count_down(n: i32) -> Free<ThunkBrand, i32> {
408			if n == 0 { Free::pure(0) } else { Free::pure(n).bind(|n| count_down(n - 1)) }
409		}
410
411		// Construct a deep chain but DO NOT run it.
412		// When `free` goes out of scope, `Drop` should handle it iteratively.
413		let _free = count_down(100_000);
414	}
415
416	/// Tests `Free::bind` on a `Roll` variant.
417	///
418	/// **What it tests:** Verifies that `bind` works correctly when applied to a suspended computation (`Roll`).
419	/// **How it tests:** Creates a `Roll` (via `roll`) and `bind`s it.
420	#[test]
421	fn test_free_bind_on_roll() {
422		let eval = Thunk::new(|| Free::pure(42));
423		let free = Free::<ThunkBrand, _>::roll(eval).bind(|x| Free::pure(x + 1));
424		assert_eq!(free.run(), 43);
425	}
426}