Skip to main content

fp_library/types/
try_lazy.rs

1use crate::types::{ArcLazyConfig, Lazy, LazyConfig, RcLazyConfig, TryThunk, TryTrampoline};
2
3/// A lazily-computed, memoized value that may fail.
4///
5/// The computation runs at most once. If it succeeds, the value is cached.
6/// If it fails, the error is cached. Subsequent accesses return the cached result.
7///
8/// ### Type Parameters
9///
10/// * `A`: The type of the computed value.
11/// * `E`: The type of the error.
12/// * `Config`: The memoization configuration.
13///
14/// ### Fields
15///
16/// * `0`: The internal lazy cell.
17pub struct TryLazy<'a, A, E, Config: LazyConfig = RcLazyConfig>(
18	pub(crate) Config::TryLazy<'a, A, E>,
19)
20where
21	A: 'a,
22	E: 'a;
23
24impl<'a, A, E, Config: LazyConfig> Clone for TryLazy<'a, A, E, Config>
25where
26	A: 'a,
27	E: 'a,
28{
29	fn clone(&self) -> Self {
30		Self(self.0.clone())
31	}
32}
33
34impl<'a, A, E, Config: LazyConfig> TryLazy<'a, A, E, Config>
35where
36	A: 'a,
37	E: 'a,
38{
39	/// Gets the memoized result, computing on first access.
40	///
41	/// ### Type Signature
42	///
43	/// `forall e a. TryLazy a e -> Result a e`
44	///
45	/// ### Returns
46	///
47	/// A result containing a reference to the value or error.
48	///
49	/// ### Examples
50	///
51	/// ```
52	/// use fp_library::types::*;
53	///
54	/// let memo = TryLazy::<_, _, RcLazyConfig>::new(|| Ok::<i32, ()>(42));
55	/// assert_eq!(memo.get(), Ok(&42));
56	/// ```
57	pub fn get(&self) -> Result<&A, &E> {
58		Config::force_try(&self.0)
59	}
60}
61
62impl<'a, A, E> TryLazy<'a, A, E, RcLazyConfig>
63where
64	A: 'a,
65	E: 'a,
66{
67	/// Creates a new TryLazy that will run `f` on first access.
68	///
69	/// ### Type Signature
70	///
71	/// `forall e a. (Unit -> Result a e) -> TryLazy a e`
72	///
73	/// ### Type Parameters
74	///
75	/// * `F`: The type of the initializer closure.
76	///
77	/// ### Parameters
78	///
79	/// * `f`: The closure that produces the result.
80	///
81	/// ### Returns
82	///
83	/// A new `TryLazy` instance.
84	///
85	/// ### Examples
86	///
87	/// ```
88	/// use fp_library::types::*;
89	///
90	/// let memo = TryLazy::<_, _, RcLazyConfig>::new(|| Ok::<i32, ()>(42));
91	/// assert_eq!(memo.get(), Ok(&42));
92	/// ```
93	pub fn new<F>(f: F) -> Self
94	where
95		F: FnOnce() -> Result<A, E> + 'a,
96	{
97		TryLazy(RcLazyConfig::new_try_lazy(Box::new(f)))
98	}
99}
100
101impl<'a, A, E> From<TryThunk<'a, A, E>> for TryLazy<'a, A, E, RcLazyConfig> {
102	fn from(eval: TryThunk<'a, A, E>) -> Self {
103		Self::new(move || eval.run())
104	}
105}
106
107impl<'a, A, E> From<TryTrampoline<A, E>> for TryLazy<'a, A, E, RcLazyConfig>
108where
109	A: Send,
110	E: Send,
111{
112	fn from(task: TryTrampoline<A, E>) -> Self {
113		Self::new(move || task.run())
114	}
115}
116
117impl<'a, A, E> From<Lazy<'a, A, ArcLazyConfig>> for TryLazy<'a, A, E, ArcLazyConfig>
118where
119	A: Clone + Send + Sync + 'a,
120	E: Send + Sync + 'a,
121{
122	fn from(memo: Lazy<'a, A, ArcLazyConfig>) -> Self {
123		Self::new(move || Ok(memo.get().clone()))
124	}
125}
126
127impl<'a, A, E> From<Lazy<'a, A, RcLazyConfig>> for TryLazy<'a, A, E, RcLazyConfig>
128where
129	A: Clone + 'a,
130	E: 'a,
131{
132	fn from(memo: Lazy<'a, A, RcLazyConfig>) -> Self {
133		Self::new(move || Ok(memo.get().clone()))
134	}
135}
136
137impl<'a, A> TryLazy<'a, A, String, RcLazyConfig>
138where
139	A: 'a,
140{
141	/// Creates a TryLazy that catches unwinds (panics).
142	///
143	/// ### Type Signature
144	///
145	/// `forall a. (Unit -> a) -> TryLazy a String`
146	///
147	/// ### Type Parameters
148	///
149	/// * `F`: The type of the initializer closure.
150	///
151	/// ### Parameters
152	///
153	/// * `f`: The closure that might panic.
154	///
155	/// ### Returns
156	///
157	/// A new `TryLazy` instance where panics are converted to `Err(String)`.
158	///
159	/// ### Examples
160	///
161	/// ```
162	/// use fp_library::types::*;
163	///
164	/// let memo = TryLazy::<_, String, RcLazyConfig>::catch_unwind(|| {
165	///     if true { panic!("oops") }
166	///     42
167	/// });
168	/// assert_eq!(memo.get(), Err(&"oops".to_string()));
169	/// ```
170	pub fn catch_unwind<F>(f: F) -> Self
171	where
172		F: FnOnce() -> A + std::panic::UnwindSafe + 'a,
173	{
174		Self::new(move || {
175			std::panic::catch_unwind(f).map_err(|e| {
176				if let Some(s) = e.downcast_ref::<&str>() {
177					s.to_string()
178				} else if let Some(s) = e.downcast_ref::<String>() {
179					s.clone()
180				} else {
181					"Unknown panic".to_string()
182				}
183			})
184		})
185	}
186}
187
188impl<'a, A, E> TryLazy<'a, A, E, ArcLazyConfig>
189where
190	A: 'a,
191	E: 'a,
192{
193	/// Creates a new TryLazy that will run `f` on first access.
194	///
195	/// ### Type Signature
196	///
197	/// `forall e a. (Unit -> Result a e) -> TryLazy a e`
198	///
199	/// ### Type Parameters
200	///
201	/// * `F`: The type of the initializer closure.
202	///
203	/// ### Parameters
204	///
205	/// * `f`: The closure that produces the result.
206	///
207	/// ### Returns
208	///
209	/// A new `TryLazy` instance.
210	///
211	/// ### Examples
212	///
213	/// ```
214	/// use fp_library::types::*;
215	///
216	/// let memo = TryLazy::<_, _, ArcLazyConfig>::new(|| Ok::<i32, ()>(42));
217	/// assert_eq!(memo.get(), Ok(&42));
218	/// ```
219	pub fn new<F>(f: F) -> Self
220	where
221		F: FnOnce() -> Result<A, E> + Send + 'a,
222	{
223		TryLazy(ArcLazyConfig::new_try_lazy(Box::new(f)))
224	}
225}
226
227/// Single-threaded fallible memoization alias.
228pub type RcTryLazy<'a, A, E> = TryLazy<'a, A, E, RcLazyConfig>;
229
230/// Thread-safe fallible memoization alias.
231pub type ArcTryLazy<'a, A, E> = TryLazy<'a, A, E, ArcLazyConfig>;
232
233#[cfg(test)]
234mod tests {
235	use crate::types::RcLazy;
236
237	use super::*;
238	use std::cell::RefCell;
239	use std::rc::Rc;
240
241	/// Tests that `TryLazy` caches successful results.
242	///
243	/// Verifies that the initializer is called only once for success.
244	#[test]
245	fn test_try_memo_caching_ok() {
246		let counter = Rc::new(RefCell::new(0));
247		let counter_clone = counter.clone();
248		let memo: RcTryLazy<i32, ()> = RcTryLazy::new(move || {
249			*counter_clone.borrow_mut() += 1;
250			Ok(42)
251		});
252
253		assert_eq!(*counter.borrow(), 0);
254		assert_eq!(memo.get(), Ok(&42));
255		assert_eq!(*counter.borrow(), 1);
256		assert_eq!(memo.get(), Ok(&42));
257		assert_eq!(*counter.borrow(), 1);
258	}
259
260	/// Tests that `TryLazy` caches error results.
261	///
262	/// Verifies that the initializer is called only once for error.
263	#[test]
264	fn test_try_memo_caching_err() {
265		let counter = Rc::new(RefCell::new(0));
266		let counter_clone = counter.clone();
267		let memo: RcTryLazy<i32, i32> = RcTryLazy::new(move || {
268			*counter_clone.borrow_mut() += 1;
269			Err(0)
270		});
271
272		assert_eq!(*counter.borrow(), 0);
273		assert_eq!(memo.get(), Err(&0));
274		assert_eq!(*counter.borrow(), 1);
275		assert_eq!(memo.get(), Err(&0));
276		assert_eq!(*counter.borrow(), 1);
277	}
278
279	/// Tests that `TryLazy` shares the cache across clones.
280	///
281	/// Verifies that clones see the same result.
282	#[test]
283	fn test_try_memo_sharing() {
284		let counter = Rc::new(RefCell::new(0));
285		let counter_clone = counter.clone();
286		let memo: RcTryLazy<i32, ()> = RcTryLazy::new(move || {
287			*counter_clone.borrow_mut() += 1;
288			Ok(42)
289		});
290		let shared = memo.clone();
291
292		assert_eq!(memo.get(), Ok(&42));
293		assert_eq!(*counter.borrow(), 1);
294		assert_eq!(shared.get(), Ok(&42));
295		assert_eq!(*counter.borrow(), 1);
296	}
297
298	/// Tests `catch_unwind`.
299	///
300	/// Verifies that panics are caught and converted to errors.
301	#[test]
302	fn test_catch_unwind() {
303		let memo = RcTryLazy::catch_unwind(|| {
304			if true {
305				panic!("oops")
306			}
307			42
308		});
309
310		match memo.get() {
311			Err(e) => assert_eq!(e, "oops"),
312			Ok(_) => panic!("Should have failed"),
313		}
314	}
315
316	/// Tests creation from `TryThunk`.
317	#[test]
318	fn test_try_memo_from_try_eval() {
319		let eval = TryThunk::new(|| Ok::<i32, ()>(42));
320		let memo = RcTryLazy::from(eval);
321		assert_eq!(memo.get(), Ok(&42));
322	}
323
324	/// Tests creation from `TryTrampoline`.
325	#[test]
326	fn test_try_memo_from_try_task() {
327		let task = TryTrampoline::<i32, ()>::ok(42);
328		let memo = RcTryLazy::from(task);
329		assert_eq!(memo.get(), Ok(&42));
330	}
331
332	/// Tests conversion to TryLazy.
333	#[test]
334	fn test_try_memo_from_rc_memo() {
335		let memo = RcLazy::new(|| 42);
336		let try_memo: crate::types::RcTryLazy<i32, ()> = crate::types::RcTryLazy::from(memo);
337		assert_eq!(try_memo.get(), Ok(&42));
338	}
339
340	/// Tests conversion to ArcTryLazy.
341	#[test]
342	fn test_try_memo_from_arc_memo() {
343		use crate::types::ArcLazy;
344		let memo = ArcLazy::new(|| 42);
345		let try_memo: crate::types::ArcTryLazy<i32, ()> = crate::types::ArcTryLazy::from(memo);
346		assert_eq!(try_memo.get(), Ok(&42));
347	}
348}