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}