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}