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