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