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