Skip to main content

fp_library/types/
try_trampoline.rs

1//! Stack-safe fallible computation type with guaranteed safety for unlimited recursion depth.
2//!
3//! Wraps [`Trampoline<Result<A, E>>`](crate::types::Trampoline) with ergonomic combinators for error handling. Provides complete stack safety for fallible computations that may recurse deeply.
4//!
5//! ### Examples
6//!
7//! ```
8//! use fp_library::types::*;
9//!
10//! let task: TryTrampoline<i32, String> = TryTrampoline::ok(10)
11//!     .map(|x| x * 2)
12//!     .bind(|x| TryTrampoline::ok(x + 5));
13//!
14//! assert_eq!(task.evaluate(), Ok(25));
15//! ```
16
17use crate::{
18	classes::Deferrable,
19	types::{Lazy, LazyConfig, Trampoline, TryLazy},
20};
21use fp_macros::{doc_params, doc_type_params, hm_signature};
22
23/// A lazy, stack-safe computation that may fail with an error.
24///
25/// This is [`Trampoline<Result<A, E>>`] with ergonomic combinators.
26///
27/// ### Type Parameters
28///
29/// * `A`: The type of the success value.
30/// * `E`: The type of the error value.
31///
32/// ### Fields
33///
34/// * `0`: The internal `Trampoline` wrapping a `Result`.
35///
36/// ### Examples
37///
38/// ```
39/// use fp_library::types::*;
40///
41/// let task: TryTrampoline<i32, String> = TryTrampoline::ok(10);
42/// assert_eq!(task.evaluate(), Ok(10));
43/// ```
44pub struct TryTrampoline<A: 'static, E: 'static>(Trampoline<Result<A, E>>);
45
46impl<A: 'static + Send, E: 'static + Send> TryTrampoline<A, E> {
47	/// Creates a successful `TryTrampoline`.
48	///
49	/// ### Type Signature
50	///
51	#[hm_signature]
52	///
53	/// ### Type Parameters
54	///
55	/// * `A`: The type of the success value.
56	/// * `E`: The type of the error value.
57	///
58	/// ### Parameters
59	///
60	#[doc_params("The success value.")]
61	///
62	/// ### Returns
63	///
64	/// A `TryTrampoline` representing success.
65	///
66	/// ### Examples
67	///
68	/// ```
69	/// use fp_library::types::*;
70	///
71	/// let task: TryTrampoline<i32, String> = TryTrampoline::ok(42);
72	/// assert_eq!(task.evaluate(), Ok(42));
73	/// ```
74	pub fn ok(a: A) -> Self {
75		TryTrampoline(Trampoline::pure(Ok(a)))
76	}
77
78	/// Creates a failed `TryTrampoline`.
79	///
80	/// ### Type Signature
81	///
82	#[hm_signature]
83	///
84	/// ### Type Parameters
85	///
86	/// * `A`: The type of the success value.
87	/// * `E`: The type of the error value.
88	///
89	/// ### Parameters
90	///
91	#[doc_params("The error value.")]
92	///
93	/// ### Returns
94	///
95	/// A `TryTrampoline` representing failure.
96	///
97	/// ### Examples
98	///
99	/// ```
100	/// use fp_library::types::*;
101	///
102	/// let task: TryTrampoline<i32, String> = TryTrampoline::err("error".to_string());
103	/// assert_eq!(task.evaluate(), Err("error".to_string()));
104	/// ```
105	pub fn err(e: E) -> Self {
106		TryTrampoline(Trampoline::pure(Err(e)))
107	}
108
109	/// Creates a lazy `TryTrampoline` that may fail.
110	///
111	/// ### Type Signature
112	///
113	#[hm_signature]
114	///
115	/// ### Type Parameters
116	///
117	#[doc_type_params("The type of the closure.")]
118	///
119	/// ### Parameters
120	///
121	#[doc_params("The closure to execute.")]
122	///
123	/// ### Returns
124	///
125	/// A `TryTrampoline` that executes `f` when run.
126	///
127	/// ### Examples
128	///
129	/// ```
130	/// use fp_library::types::*;
131	///
132	/// let task: TryTrampoline<i32, String> = TryTrampoline::new(|| Ok(42));
133	/// assert_eq!(task.evaluate(), Ok(42));
134	/// ```
135	pub fn new<F>(f: F) -> Self
136	where
137		F: FnOnce() -> Result<A, E> + 'static,
138	{
139		TryTrampoline(Trampoline::new(f))
140	}
141
142	/// Defers the construction of a `TryTrampoline`.
143	///
144	/// Use this for stack-safe recursion.
145	///
146	/// ### Type Signature
147	///
148	#[hm_signature]
149	///
150	/// ### Type Parameters
151	///
152	#[doc_type_params("The type of the thunk.")]
153	///
154	/// ### Parameters
155	///
156	#[doc_params("A thunk that returns the next step.")]
157	///
158	/// ### Returns
159	///
160	/// A `TryTrampoline` that executes `f` to get the next step.
161	///
162	/// ### Examples
163	///
164	/// ```
165	/// use fp_library::types::*;
166	///
167	/// let task: TryTrampoline<i32, String> = TryTrampoline::defer(|| TryTrampoline::ok(42));
168	/// assert_eq!(task.evaluate(), Ok(42));
169	/// ```
170	///
171	/// Stack-safe recursion:
172	///
173	/// ```
174	/// use fp_library::types::*;
175	///
176	/// fn factorial(n: i32, acc: i32) -> TryTrampoline<i32, String> {
177	///     if n < 0 {
178	///         TryTrampoline::err("Negative input".to_string())
179	///     } else if n == 0 {
180	///         TryTrampoline::ok(acc)
181	///     } else {
182	///         TryTrampoline::defer(move || factorial(n - 1, n * acc))
183	///     }
184	/// }
185	///
186	/// let task = factorial(5, 1);
187	/// assert_eq!(task.evaluate(), Ok(120));
188	/// ```
189	pub fn defer<F>(f: F) -> Self
190	where
191		F: FnOnce() -> TryTrampoline<A, E> + 'static,
192	{
193		TryTrampoline(Trampoline::defer(move || f().0))
194	}
195
196	/// Maps over the success value.
197	///
198	/// ### Type Signature
199	///
200	#[hm_signature]
201	///
202	/// ### Type Parameters
203	///
204	#[doc_type_params(
205		"The type of the new success value.",
206		("F", "The type of the mapping function.")
207	)]
208	///
209	/// ### Parameters
210	///
211	#[doc_params("The function to apply to the success value.")]
212	///
213	/// ### Returns
214	///
215	/// A new `TryTrampoline` with the transformed success value.
216	///
217	/// ### Examples
218	///
219	/// ```
220	/// use fp_library::types::*;
221	///
222	/// let task: TryTrampoline<i32, String> = TryTrampoline::ok(10).map(|x| x * 2);
223	/// assert_eq!(task.evaluate(), Ok(20));
224	/// ```
225	pub fn map<B: 'static + Send, Func>(
226		self,
227		func: Func,
228	) -> TryTrampoline<B, E>
229	where
230		Func: FnOnce(A) -> B + 'static,
231	{
232		TryTrampoline(self.0.map(|result| result.map(func)))
233	}
234
235	/// Maps over the error value.
236	///
237	/// ### Type Signature
238	///
239	#[hm_signature]
240	///
241	/// ### Type Parameters
242	///
243	#[doc_type_params(
244		"The type of the new error value.",
245		("F", "The type of the mapping function.")
246	)]
247	///
248	/// ### Parameters
249	///
250	#[doc_params("The function to apply to the error value.")]
251	///
252	/// ### Returns
253	///
254	/// A new `TryTrampoline` with the transformed error value.
255	///
256	/// ### Examples
257	///
258	/// ```
259	/// use fp_library::types::*;
260	///
261	/// let task: TryTrampoline<i32, String> = TryTrampoline::err("error".to_string())
262	///     .map_err(|e| e.to_uppercase());
263	/// assert_eq!(task.evaluate(), Err("ERROR".to_string()));
264	/// ```
265	pub fn map_err<E2: 'static + Send, Func>(
266		self,
267		func: Func,
268	) -> TryTrampoline<A, E2>
269	where
270		Func: FnOnce(E) -> E2 + 'static,
271	{
272		TryTrampoline(self.0.map(|result| result.map_err(func)))
273	}
274
275	/// Chains fallible computations.
276	///
277	/// ### Type Signature
278	///
279	#[hm_signature]
280	///
281	/// ### Type Parameters
282	///
283	#[doc_type_params("The type of the new success value.", "The type of the binding function.")]
284	///
285	/// ### Parameters
286	///
287	#[doc_params("The function to apply to the success value.")]
288	///
289	/// ### Returns
290	///
291	/// A new `TryTrampoline` that chains `f` after this task.
292	///
293	/// ### Examples
294	///
295	/// ```
296	/// use fp_library::types::*;
297	///
298	/// let task: TryTrampoline<i32, String> = TryTrampoline::ok(10).bind(|x| TryTrampoline::ok(x * 2));
299	/// assert_eq!(task.evaluate(), Ok(20));
300	/// ```
301	pub fn bind<B: 'static + Send, F>(
302		self,
303		f: F,
304	) -> TryTrampoline<B, E>
305	where
306		F: FnOnce(A) -> TryTrampoline<B, E> + 'static,
307	{
308		TryTrampoline(self.0.bind(|result| match result {
309			Ok(a) => f(a).0,
310			Err(e) => Trampoline::pure(Err(e)),
311		}))
312	}
313
314	/// Recovers from an error.
315	///
316	/// ### Type Signature
317	///
318	#[hm_signature]
319	///
320	/// ### Type Parameters
321	///
322	#[doc_type_params("The type of the recovery function.")]
323	///
324	/// ### Parameters
325	///
326	#[doc_params("The function to apply to the error value.")]
327	///
328	/// ### Returns
329	///
330	/// A new `TryTrampoline` that attempts to recover from failure.
331	///
332	/// ### Examples
333	///
334	/// ```
335	/// use fp_library::types::*;
336	///
337	/// let task: TryTrampoline<i32, String> = TryTrampoline::err("error".to_string())
338	///     .catch(|_| TryTrampoline::ok(42));
339	/// assert_eq!(task.evaluate(), Ok(42));
340	/// ```
341	pub fn catch<F>(
342		self,
343		f: F,
344	) -> Self
345	where
346		F: FnOnce(E) -> TryTrampoline<A, E> + 'static,
347	{
348		TryTrampoline(self.0.bind(|result| match result {
349			Ok(a) => Trampoline::pure(Ok(a)),
350			Err(e) => f(e).0,
351		}))
352	}
353
354	/// Runs the computation, returning the result.
355	///
356	/// ### Type Signature
357	///
358	#[hm_signature]
359	///
360	/// ### Returns
361	///
362	/// The result of the computation.
363	///
364	/// ### Examples
365	///
366	/// ```
367	/// use fp_library::types::*;
368	///
369	/// let task: TryTrampoline<i32, String> = TryTrampoline::ok(42);
370	/// assert_eq!(task.evaluate(), Ok(42));
371	/// ```
372	pub fn evaluate(self) -> Result<A, E> {
373		self.0.evaluate()
374	}
375}
376
377impl<A, E> From<Trampoline<A>> for TryTrampoline<A, E>
378where
379	A: Send + 'static,
380	E: Send + 'static,
381{
382	fn from(task: Trampoline<A>) -> Self {
383		TryTrampoline::new(move || Ok(task.evaluate()))
384	}
385}
386
387impl<A, E, Config> From<Lazy<'static, A, Config>> for TryTrampoline<A, E>
388where
389	A: Clone + Send + 'static,
390	E: Send + 'static,
391	Config: LazyConfig,
392{
393	fn from(memo: Lazy<'static, A, Config>) -> Self {
394		TryTrampoline::new(move || Ok(memo.evaluate().clone()))
395	}
396}
397
398impl<A, E, Config> From<TryLazy<'static, A, E, Config>> for TryTrampoline<A, E>
399where
400	A: Clone + Send + 'static,
401	E: Clone + Send + 'static,
402	Config: LazyConfig,
403{
404	fn from(memo: TryLazy<'static, A, E, Config>) -> Self {
405		TryTrampoline::new(move || memo.evaluate().cloned().map_err(Clone::clone))
406	}
407}
408
409impl<A, E> Deferrable<'static> for TryTrampoline<A, E>
410where
411	A: 'static + Send,
412	E: 'static + Send,
413{
414	/// Creates a value from a computation that produces the value.
415	///
416	/// ### Type Signature
417	///
418	#[hm_signature(Deferrable)]
419	///
420	/// ### Type Parameters
421	///
422	#[doc_type_params("The type of the thunk.")]
423	///
424	/// ### Parameters
425	///
426	#[doc_params("A thunk that produces the value.")]
427	///
428	/// ### Returns
429	///
430	/// The deferred value.
431	///
432	/// ### Examples
433	///
434	/// ```
435	/// use fp_library::{brands::*, functions::*, types::*, classes::Deferrable};
436	///
437	/// let task: TryTrampoline<i32, String> = Deferrable::defer(|| TryTrampoline::ok(42));
438	/// assert_eq!(task.evaluate(), Ok(42));
439	/// ```
440	fn defer<F>(f: F) -> Self
441	where
442		F: FnOnce() -> Self + 'static,
443		Self: Sized,
444	{
445		TryTrampoline(Trampoline::defer(move || f().0))
446	}
447}
448
449#[cfg(test)]
450mod tests {
451	use super::*;
452
453	/// Tests `TryTrampoline::ok`.
454	///
455	/// Verifies that `ok` creates a successful task.
456	#[test]
457	fn test_try_task_ok() {
458		let task: TryTrampoline<i32, String> = TryTrampoline::ok(42);
459		assert_eq!(task.evaluate(), Ok(42));
460	}
461
462	/// Tests `TryTrampoline::err`.
463	///
464	/// Verifies that `err` creates a failed task.
465	#[test]
466	fn test_try_task_err() {
467		let task: TryTrampoline<i32, String> = TryTrampoline::err("error".to_string());
468		assert_eq!(task.evaluate(), Err("error".to_string()));
469	}
470
471	/// Tests `TryTrampoline::map`.
472	///
473	/// Verifies that `map` transforms the success value.
474	#[test]
475	fn test_try_task_map() {
476		let task: TryTrampoline<i32, String> = TryTrampoline::ok(10).map(|x| x * 2);
477		assert_eq!(task.evaluate(), Ok(20));
478	}
479
480	/// Tests `TryTrampoline::map_err`.
481	///
482	/// Verifies that `map_err` transforms the error value.
483	#[test]
484	fn test_try_task_map_err() {
485		let task: TryTrampoline<i32, String> =
486			TryTrampoline::err("error".to_string()).map_err(|e| e.to_uppercase());
487		assert_eq!(task.evaluate(), Err("ERROR".to_string()));
488	}
489
490	/// Tests `TryTrampoline::bind`.
491	///
492	/// Verifies that `bind` chains computations.
493	#[test]
494	fn test_try_task_bind() {
495		let task: TryTrampoline<i32, String> =
496			TryTrampoline::ok(10).bind(|x| TryTrampoline::ok(x * 2));
497		assert_eq!(task.evaluate(), Ok(20));
498	}
499
500	/// Tests `TryTrampoline::or_else`.
501	///
502	/// Verifies that `or_else` recovers from failure.
503	#[test]
504	fn test_try_task_or_else() {
505		let task: TryTrampoline<i32, String> =
506			TryTrampoline::err("error".to_string()).catch(|_| TryTrampoline::ok(42));
507		assert_eq!(task.evaluate(), Ok(42));
508	}
509
510	/// Tests `TryTrampoline::new`.
511	///
512	/// Verifies that `new` creates a lazy task.
513	#[test]
514	fn test_try_task_new() {
515		let task: TryTrampoline<i32, String> = TryTrampoline::new(|| Ok(42));
516		assert_eq!(task.evaluate(), Ok(42));
517	}
518
519	/// Tests `From<Trampoline>`.
520	#[test]
521	fn test_try_task_from_task() {
522		let task = Trampoline::pure(42);
523		let try_task: TryTrampoline<i32, String> = TryTrampoline::from(task);
524		assert_eq!(try_task.evaluate(), Ok(42));
525	}
526
527	/// Tests `From<Lazy>`.
528	#[test]
529	fn test_try_task_from_memo() {
530		use crate::types::ArcLazy;
531		let memo = ArcLazy::new(|| 42);
532		let try_task: TryTrampoline<i32, String> = TryTrampoline::from(memo);
533		assert_eq!(try_task.evaluate(), Ok(42));
534	}
535
536	/// Tests `From<TryLazy>`.
537	#[test]
538	fn test_try_task_from_try_memo() {
539		use crate::types::ArcTryLazy;
540		let memo = ArcTryLazy::new(|| Ok(42));
541		let try_task: TryTrampoline<i32, String> = TryTrampoline::from(memo);
542		assert_eq!(try_task.evaluate(), Ok(42));
543	}
544}