fp_library/types/try_thunk.rs
1use crate::types::{Lazy, LazyConfig, Thunk, TryLazy};
2
3/// A deferred computation that may fail with error type `E`.
4///
5/// Like [`Thunk`], this is NOT memoized. Each [`TryThunk::run`] re-executes.
6/// Unlike [`Thunk`], the result is [`Result<A, E>`].
7///
8/// ### Type Parameters
9///
10/// * `A`: The type of the value produced by the computation on success.
11/// * `E`: The type of the error produced by the computation on failure.
12///
13/// ### Fields
14///
15/// * `0`: The closure that performs the computation.
16///
17/// ### Examples
18///
19/// ```
20/// use fp_library::types::*;
21///
22/// let computation: TryThunk<i32, &str> = TryThunk::new(|| {
23/// Ok(42)
24/// });
25///
26/// match computation.run() {
27/// Ok(val) => assert_eq!(val, 42),
28/// Err(_) => panic!("Should not fail"),
29/// }
30/// ```
31pub struct TryThunk<'a, A, E>(Box<dyn FnOnce() -> Result<A, E> + 'a>);
32
33impl<'a, A: 'a, E: 'a> TryThunk<'a, A, E> {
34 /// Creates a new TryThunk from a thunk.
35 ///
36 /// ### Type Signature
37 ///
38 /// `forall e a. (Unit -> Result a e) -> TryThunk a e`
39 ///
40 /// ### Type Parameters
41 ///
42 /// * `F`: The type of the thunk.
43 ///
44 /// ### Parameters
45 ///
46 /// * `f`: The thunk to wrap.
47 ///
48 /// ### Returns
49 ///
50 /// A new `TryThunk` instance.
51 ///
52 /// ### Examples
53 ///
54 /// ```
55 /// use fp_library::types::*;
56 ///
57 /// let try_eval: TryThunk<i32, ()> = TryThunk::new(|| Ok(42));
58 /// assert_eq!(try_eval.run(), Ok(42));
59 /// ```
60 pub fn new<F>(f: F) -> Self
61 where
62 F: FnOnce() -> Result<A, E> + 'a,
63 {
64 TryThunk(Box::new(f))
65 }
66
67 /// Returns a pure value (already computed).
68 ///
69 /// ### Type Signature
70 ///
71 /// `forall e a. a -> TryThunk a e`
72 ///
73 /// ### Parameters
74 ///
75 /// * `a`: The value to wrap.
76 ///
77 /// ### Returns
78 ///
79 /// A new `TryThunk` instance containing the value.
80 ///
81 /// ### Examples
82 ///
83 /// ```
84 /// use fp_library::types::*;
85 ///
86 /// let try_eval: TryThunk<i32, ()> = TryThunk::pure(42);
87 /// assert_eq!(try_eval.run(), Ok(42));
88 /// ```
89 pub fn pure(a: A) -> Self
90 where
91 A: 'a,
92 {
93 TryThunk::new(move || Ok(a))
94 }
95
96 /// Alias for [`pure`](Self::pure).
97 ///
98 /// Creates a successful computation.
99 ///
100 /// ### Type Signature
101 ///
102 /// `forall e a. a -> TryThunk a e`
103 ///
104 /// ### Parameters
105 ///
106 /// * `a`: The value to wrap.
107 ///
108 /// ### Returns
109 ///
110 /// A new `TryThunk` instance containing the value.
111 ///
112 /// ### Examples
113 ///
114 /// ```
115 /// use fp_library::types::*;
116 ///
117 /// let try_eval: TryThunk<i32, ()> = TryThunk::ok(42);
118 /// assert_eq!(try_eval.run(), Ok(42));
119 /// ```
120 pub fn ok(a: A) -> Self
121 where
122 A: 'a,
123 {
124 Self::pure(a)
125 }
126
127 /// Returns a pure error.
128 ///
129 /// ### Type Signature
130 ///
131 /// `forall e a. e -> TryThunk a e`
132 ///
133 /// ### Parameters
134 ///
135 /// * `e`: The error to wrap.
136 ///
137 /// ### Returns
138 ///
139 /// A new `TryThunk` instance containing the error.
140 ///
141 /// ### Examples
142 ///
143 /// ```
144 /// use fp_library::types::*;
145 ///
146 /// let try_eval: TryThunk<i32, &str> = TryThunk::err("error");
147 /// assert_eq!(try_eval.run(), Err("error"));
148 /// ```
149 pub fn err(e: E) -> Self
150 where
151 E: 'a,
152 {
153 TryThunk::new(move || Err(e))
154 }
155
156 /// Monadic bind: chains computations.
157 ///
158 /// ### Type Signature
159 ///
160 /// `forall e b a. (a -> TryThunk b e, TryThunk a e) -> TryThunk b e`
161 ///
162 /// ### Type Parameters
163 ///
164 /// * `B`: The type of the result of the new computation.
165 /// * `F`: The type of the function to apply.
166 ///
167 /// ### Parameters
168 ///
169 /// * `f`: The function to apply to the result of the computation.
170 ///
171 /// ### Returns
172 ///
173 /// A new `TryThunk` instance representing the chained computation.
174 ///
175 /// ### Examples
176 ///
177 /// ```
178 /// use fp_library::types::*;
179 ///
180 /// let try_eval: TryThunk<i32, ()> = TryThunk::pure(21).bind(|x| TryThunk::pure(x * 2));
181 /// assert_eq!(try_eval.run(), Ok(42));
182 /// ```
183 pub fn bind<B: 'a, F>(
184 self,
185 f: F,
186 ) -> TryThunk<'a, B, E>
187 where
188 F: FnOnce(A) -> TryThunk<'a, B, E> + 'a,
189 {
190 TryThunk::new(move || match (self.0)() {
191 Ok(a) => (f(a).0)(),
192 Err(e) => Err(e),
193 })
194 }
195
196 /// Alias for [`bind`](Self::bind).
197 ///
198 /// Chains computations.
199 ///
200 /// ### Type Signature
201 ///
202 /// `forall e b a. (a -> TryThunk b e, TryThunk a e) -> TryThunk b e`
203 ///
204 /// ### Type Parameters
205 ///
206 /// * `B`: The type of the result of the new computation.
207 /// * `F`: The type of the function to apply.
208 ///
209 /// ### Parameters
210 ///
211 /// * `f`: The function to apply to the result of the computation.
212 ///
213 /// ### Returns
214 ///
215 /// A new `TryThunk` instance representing the chained computation.
216 ///
217 /// ### Examples
218 ///
219 /// ```
220 /// use fp_library::types::*;
221 ///
222 /// let try_eval: TryThunk<i32, ()> = TryThunk::ok(21).and_then(|x| TryThunk::ok(x * 2));
223 /// assert_eq!(try_eval.run(), Ok(42));
224 /// ```
225 pub fn and_then<B: 'a, F>(
226 self,
227 f: F,
228 ) -> TryThunk<'a, B, E>
229 where
230 F: FnOnce(A) -> TryThunk<'a, B, E> + 'a,
231 {
232 self.bind(f)
233 }
234
235 /// Functor map: transforms the result.
236 ///
237 /// ### Type Signature
238 ///
239 /// `forall e b a. (a -> b, TryThunk a e) -> TryThunk b e`
240 ///
241 /// ### Type Parameters
242 ///
243 /// * `B`: The type of the result of the transformation.
244 /// * `F`: The type of the transformation function.
245 ///
246 /// ### Parameters
247 ///
248 /// * `f`: The function to apply to the result of the computation.
249 ///
250 /// ### Returns
251 ///
252 /// A new `TryThunk` instance with the transformed result.
253 ///
254 /// ### Examples
255 ///
256 /// ```
257 /// use fp_library::types::*;
258 ///
259 /// let try_eval: TryThunk<i32, ()> = TryThunk::pure(21).map(|x| x * 2);
260 /// assert_eq!(try_eval.run(), Ok(42));
261 /// ```
262 pub fn map<B: 'a, F>(
263 self,
264 f: F,
265 ) -> TryThunk<'a, B, E>
266 where
267 F: FnOnce(A) -> B + 'a,
268 {
269 TryThunk::new(move || (self.0)().map(f))
270 }
271
272 /// Map error: transforms the error.
273 ///
274 /// ### Type Signature
275 ///
276 /// `forall e2 e a. (e -> e2, TryThunk a e) -> TryThunk a e2`
277 ///
278 /// ### Type Parameters
279 ///
280 /// * `E2`: The type of the new error.
281 /// * `F`: The type of the transformation function.
282 ///
283 /// ### Parameters
284 ///
285 /// * `f`: The function to apply to the error.
286 ///
287 /// ### Returns
288 ///
289 /// A new `TryThunk` instance with the transformed error.
290 ///
291 /// ### Examples
292 ///
293 /// ```
294 /// use fp_library::types::*;
295 ///
296 /// let try_eval: TryThunk<i32, i32> = TryThunk::err(21).map_err(|x| x * 2);
297 /// assert_eq!(try_eval.run(), Err(42));
298 /// ```
299 pub fn map_err<E2: 'a, F>(
300 self,
301 f: F,
302 ) -> TryThunk<'a, A, E2>
303 where
304 F: FnOnce(E) -> E2 + 'a,
305 {
306 TryThunk::new(move || (self.0)().map_err(f))
307 }
308
309 /// Forces evaluation and returns the result.
310 ///
311 /// ### Type Signature
312 ///
313 /// `forall e a. TryThunk a e -> Result a e`
314 ///
315 /// ### Returns
316 ///
317 /// The result of the computation.
318 ///
319 /// ### Examples
320 ///
321 /// ```
322 /// use fp_library::types::*;
323 ///
324 /// let try_eval: TryThunk<i32, ()> = TryThunk::pure(42);
325 /// assert_eq!(try_eval.run(), Ok(42));
326 /// ```
327 pub fn run(self) -> Result<A, E> {
328 (self.0)()
329 }
330}
331
332impl<'a, A, E, Config> From<Lazy<'a, A, Config>> for TryThunk<'a, A, E>
333where
334 A: Clone + 'a,
335 E: 'a,
336 Config: LazyConfig,
337{
338 fn from(memo: Lazy<'a, A, Config>) -> Self {
339 TryThunk::new(move || Ok(memo.get().clone()))
340 }
341}
342
343impl<'a, A, E, Config> From<TryLazy<'a, A, E, Config>> for TryThunk<'a, A, E>
344where
345 A: Clone + 'a,
346 E: Clone + 'a,
347 Config: LazyConfig,
348{
349 fn from(memo: TryLazy<'a, A, E, Config>) -> Self {
350 TryThunk::new(move || memo.get().cloned().map_err(Clone::clone))
351 }
352}
353
354impl<'a, A: 'a, E: 'a> From<Thunk<'a, A>> for TryThunk<'a, A, E> {
355 fn from(eval: Thunk<'a, A>) -> Self {
356 TryThunk::new(move || Ok(eval.run()))
357 }
358}
359
360#[cfg(test)]
361mod tests {
362 use super::*;
363
364 /// Tests success path.
365 ///
366 /// Verifies that `TryThunk::pure` creates a successful computation.
367 #[test]
368 fn test_success() {
369 let try_eval: TryThunk<i32, ()> = TryThunk::pure(42);
370 assert_eq!(try_eval.run(), Ok(42));
371 }
372
373 /// Tests failure path.
374 ///
375 /// Verifies that `TryThunk::err` creates a failed computation.
376 #[test]
377 fn test_failure() {
378 let try_eval: TryThunk<i32, &str> = TryThunk::err("error");
379 assert_eq!(try_eval.run(), Err("error"));
380 }
381
382 /// Tests `TryThunk::map`.
383 ///
384 /// Verifies that `map` transforms the success value.
385 #[test]
386 fn test_map() {
387 let try_eval: TryThunk<i32, ()> = TryThunk::pure(21).map(|x| x * 2);
388 assert_eq!(try_eval.run(), Ok(42));
389 }
390
391 /// Tests `TryThunk::map_err`.
392 ///
393 /// Verifies that `map_err` transforms the error value.
394 #[test]
395 fn test_map_err() {
396 let try_eval: TryThunk<i32, i32> = TryThunk::err(21).map_err(|x| x * 2);
397 assert_eq!(try_eval.run(), Err(42));
398 }
399
400 /// Tests `TryThunk::bind`.
401 ///
402 /// Verifies that `bind` chains computations.
403 #[test]
404 fn test_bind() {
405 let try_eval: TryThunk<i32, ()> = TryThunk::pure(21).bind(|x| TryThunk::pure(x * 2));
406 assert_eq!(try_eval.run(), Ok(42));
407 }
408
409 /// Tests borrowing in TryThunk.
410 ///
411 /// Verifies that `TryThunk` can capture references.
412 #[test]
413 fn test_borrowing() {
414 let x = 42;
415 let try_eval: TryThunk<&i32, ()> = TryThunk::new(|| Ok(&x));
416 assert_eq!(try_eval.run(), Ok(&42));
417 }
418
419 /// Tests `TryThunk::bind` failure propagation.
420 ///
421 /// Verifies that if the first computation fails, the second one is not executed.
422 #[test]
423 fn test_bind_failure() {
424 let try_eval = TryThunk::<i32, &str>::err("error").bind(|x| TryThunk::pure(x * 2));
425 assert_eq!(try_eval.run(), Err("error"));
426 }
427
428 /// Tests `TryThunk::map` failure propagation.
429 ///
430 /// Verifies that `map` is not executed if the computation fails.
431 #[test]
432 fn test_map_failure() {
433 let try_eval = TryThunk::<i32, &str>::err("error").map(|x| x * 2);
434 assert_eq!(try_eval.run(), Err("error"));
435 }
436
437 /// Tests `TryThunk::map_err` success propagation.
438 ///
439 /// Verifies that `map_err` is not executed if the computation succeeds.
440 #[test]
441 fn test_map_err_success() {
442 let try_eval = TryThunk::<i32, &str>::pure(42).map_err(|_| "new error");
443 assert_eq!(try_eval.run(), Ok(42));
444 }
445
446 /// Tests `From<Lazy>`.
447 #[test]
448 fn test_try_eval_from_memo() {
449 use crate::types::RcLazy;
450 let memo = RcLazy::new(|| 42);
451 let try_eval: TryThunk<i32, ()> = TryThunk::from(memo);
452 assert_eq!(try_eval.run(), Ok(42));
453 }
454
455 /// Tests `From<TryLazy>`.
456 #[test]
457 fn test_try_eval_from_try_memo() {
458 use crate::types::RcTryLazy;
459 let memo = RcTryLazy::new(|| Ok(42));
460 let try_eval: TryThunk<i32, ()> = TryThunk::from(memo);
461 assert_eq!(try_eval.run(), Ok(42));
462 }
463
464 /// Tests `Thunk::into_try`.
465 ///
466 /// Verifies that `From<Thunk>` converts an `Thunk` into a `TryThunk` that succeeds.
467 #[test]
468 fn test_try_eval_from_eval() {
469 let eval = Thunk::pure(42);
470 let try_eval: TryThunk<i32, ()> = TryThunk::from(eval);
471 assert_eq!(try_eval.run(), Ok(42));
472 }
473}