Skip to main content

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}