fp_library/types/lazy.rs
1//! Lazy value wrapper.
2//!
3//! This module defines the [`Lazy`] struct, which represents a lazily-computed, memoized value.
4//! It implements [`Semigroup`], [`Monoid`], and [`Defer`].
5//!
6//! ## Configurations
7//!
8//! - [`RcLazyConfig`] / [`RcLazy`]: Single-threaded lazy values using [`Rc`](std::rc::Rc). Not thread-safe.
9//! - [`ArcLazyConfig`] / [`ArcLazy`]: Thread-safe lazy values using [`Arc`]. Requires `A: Send + Sync`.
10
11use crate::{
12 brands::{ArcBrand, ArcFnBrand, LazyBrand, OnceCellBrand, OnceLockBrand, RcBrand, RcFnBrand},
13 classes::{
14 cloneable_fn::CloneableFn, defer::Defer, monoid::Monoid, once::Once,
15 ref_counted_pointer::RefCountedPointer, semigroup::Semigroup,
16 send_cloneable_fn::SendCloneableFn, thunk_wrapper::ThunkWrapper,
17 },
18 impl_kind,
19 kinds::*,
20};
21use std::{
22 fmt::{self, Debug, Formatter},
23 ops::Deref,
24 sync::Arc,
25};
26use thiserror::Error;
27
28/// Configuration trait for `Lazy` types.
29///
30/// This trait defines the types used for pointer storage, memoization, and thunk execution.
31/// It ensures that compatible types are used together (e.g., `Rc` with `OnceCell`, `Arc` with `OnceLock`).
32pub trait LazyConfig: Sized + 'static {
33 /// The pointer brand for shared ownership (e.g., `RcBrand`, `ArcBrand`).
34 type PtrBrand: RefCountedPointer + ThunkWrapper;
35 /// The once-cell brand for memoization (e.g., `OnceCellBrand`, `OnceLockBrand`).
36 type OnceBrand: Once;
37 /// The function brand for thunk storage (e.g., `RcFnBrand`, `ArcFnBrand`).
38 type FnBrand: CloneableFn;
39 /// The thunk type to use for this configuration.
40 /// Thunks deref to `Fn(()) -> A` to match the cloneable function wrapper.
41 type ThunkOf<'a, A>: Clone + Deref<Target: Fn(()) -> A>
42 where
43 A: 'a;
44}
45
46/// Trait for `Lazy` configurations that support semigroup operations.
47pub trait LazySemigroup<A>: LazyConfig {
48 /// Combines two lazy values.
49 ///
50 /// This method combines two lazy values into a new lazy value.
51 ///
52 /// ### Type Signature
53 ///
54 /// `forall config a. Semigroup a => (Lazy config a, Lazy config a) -> Lazy config a`
55 ///
56 /// ### Parameters
57 ///
58 /// * `x`: The first lazy value.
59 /// * `y`: The second lazy value.
60 ///
61 /// ### Returns
62 ///
63 /// A new lazy value that combines the results.
64 ///
65 /// ### Examples
66 ///
67 /// ```
68 /// use fp_library::types::lazy::*;
69 ///
70 /// let x = RcLazy::new(RcLazyConfig::new_thunk(|_| "Hello, ".to_string()));
71 /// let y = RcLazy::new(RcLazyConfig::new_thunk(|_| "World!".to_string()));
72 /// // Note: LazySemigroup::append is usually called via Semigroup::append on the Lazy type
73 /// let z = <RcLazyConfig as LazySemigroup<String>>::append(x, y);
74 /// assert_eq!(Lazy::force_or_panic(&z), "Hello, World!".to_string());
75 /// ```
76 fn append<'a>(
77 x: Lazy<'a, Self, A>,
78 y: Lazy<'a, Self, A>,
79 ) -> Lazy<'a, Self, A>
80 where
81 A: Semigroup + Clone + 'a;
82}
83
84/// Trait for `Lazy` configurations that support monoid operations.
85pub trait LazyMonoid<A>: LazySemigroup<A> {
86 /// Returns the identity element.
87 ///
88 /// This method returns a lazy value that evaluates to the identity element.
89 ///
90 /// ### Type Signature
91 ///
92 /// `forall config a. Monoid a => () -> Lazy config a`
93 ///
94 /// ### Returns
95 ///
96 /// A lazy value containing the identity element.
97 ///
98 /// ### Examples
99 ///
100 /// ```
101 /// use fp_library::types::lazy::*;
102 ///
103 /// let x = <RcLazyConfig as LazyMonoid<String>>::empty();
104 /// assert_eq!(Lazy::force_or_panic(&x), "".to_string());
105 /// ```
106 fn empty<'a>() -> Lazy<'a, Self, A>
107 where
108 A: Monoid + Clone + 'a;
109}
110
111/// Trait for `Lazy` configurations that support defer operations.
112pub trait LazyDefer<'a, A>: LazyConfig {
113 /// Creates a value from a computation that produces the value.
114 ///
115 /// This method defers the construction of a `Lazy` value.
116 ///
117 /// ### Type Signature
118 ///
119 /// `forall config a. (() -> Lazy config a) -> Lazy config a`
120 ///
121 /// ### Type Parameters
122 ///
123 /// * `FnBrand`: The brand of the cloneable function wrapper.
124 ///
125 /// ### Parameters
126 ///
127 /// * `f`: A thunk (wrapped in a cloneable function) that produces the value.
128 ///
129 /// ### Returns
130 ///
131 /// A new lazy value.
132 ///
133 /// ### Examples
134 ///
135 /// ```
136 /// use fp_library::{brands::*, functions::*, types::lazy::*};
137 ///
138 /// let lazy = <RcLazyConfig as LazyDefer<i32>>::defer::<RcFnBrand>(
139 /// cloneable_fn_new::<RcFnBrand, _, _>(|_| RcLazy::new(RcLazyConfig::new_thunk(|_| 42)))
140 /// );
141 /// assert_eq!(Lazy::force_or_panic(&lazy), 42);
142 /// ```
143 fn defer<FnBrand>(
144 f: <FnBrand as CloneableFn>::Of<'a, (), Lazy<'a, Self, A>>
145 ) -> Lazy<'a, Self, A>
146 where
147 FnBrand: CloneableFn + 'a,
148 A: Clone + 'a;
149}
150
151// =============================================================================
152// RcLazyConfig - Single-threaded lazy values
153// =============================================================================
154
155/// Configuration for `Rc`-based `Lazy` values.
156///
157/// Uses `Rc` for shared ownership, `OnceCell` for memoization, and `RcFn` for thunks.
158/// This configuration is **not thread-safe**.
159///
160/// ### Examples
161///
162/// ```
163/// use fp_library::types::lazy::*;
164///
165/// let lazy = RcLazy::new(RcLazyConfig::new_thunk(|_| 42));
166/// assert_eq!(Lazy::force_or_panic(&lazy), 42);
167/// ```
168#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
169pub struct RcLazyConfig;
170
171impl RcLazyConfig {
172 /// Creates a new thunk from a closure.
173 ///
174 /// ### Type Signature
175 ///
176 /// `forall a. (Fn(()) -> a) -> ThunkOf a`
177 ///
178 /// ### Type Parameters
179 ///
180 /// * `A`: The return type of the closure.
181 /// * `F`: The closure type.
182 ///
183 /// ### Parameters
184 ///
185 /// * `f`: The closure to wrap.
186 ///
187 /// ### Returns
188 ///
189 /// A new thunk.
190 ///
191 /// ### Examples
192 ///
193 /// ```
194 /// use fp_library::types::lazy::*;
195 ///
196 /// let thunk = RcLazyConfig::new_thunk(|_| 42);
197 /// assert_eq!(thunk(()), 42);
198 /// ```
199 pub fn new_thunk<'a, A, F>(f: F) -> <Self as LazyConfig>::ThunkOf<'a, A>
200 where
201 A: 'a,
202 F: Fn(()) -> A + Clone + 'a,
203 {
204 <RcFnBrand as CloneableFn>::new(f)
205 }
206}
207
208impl LazyConfig for RcLazyConfig {
209 type PtrBrand = RcBrand;
210 type OnceBrand = OnceCellBrand;
211 type FnBrand = RcFnBrand;
212 type ThunkOf<'a, A>
213 = <RcFnBrand as CloneableFn>::Of<'a, (), A>
214 where
215 A: 'a;
216}
217
218impl<A> LazySemigroup<A> for RcLazyConfig {
219 /// Combines two lazy values.
220 ///
221 /// The combination is itself lazy: the result is a new thunk that, when forced,
222 /// forces both input values and combines them.
223 ///
224 /// ### Type Signature
225 ///
226 /// `forall a. Semigroup a => (RcLazy a, RcLazy a) -> RcLazy a`
227 ///
228 /// ### Parameters
229 ///
230 /// * `x`: The first lazy value.
231 /// * `y`: The second lazy value.
232 ///
233 /// ### Returns
234 ///
235 /// A new lazy value that combines the results.
236 ///
237 /// ### Examples
238 ///
239 /// ```
240 /// use fp_library::types::lazy::*;
241 ///
242 /// let x = ArcLazy::new(ArcLazyConfig::new_thunk(|_| "Hello, ".to_string()));
243 /// let y = ArcLazy::new(ArcLazyConfig::new_thunk(|_| "World!".to_string()));
244 /// let z = ArcLazyConfig::append(x, y);
245 /// assert_eq!(Lazy::force_or_panic(&z), "Hello, World!".to_string());
246 /// ```
247 fn append<'a>(
248 x: Lazy<'a, Self, A>,
249 y: Lazy<'a, Self, A>,
250 ) -> Lazy<'a, Self, A>
251 where
252 A: Semigroup + Clone + 'a,
253 {
254 let thunk = Self::new_thunk(move |_| {
255 let x_val = match Lazy::force(&x) {
256 Ok(v) => v.clone(),
257 Err(e) => std::panic::resume_unwind(Box::new(e.to_string())),
258 };
259 let y_val = match Lazy::force(&y) {
260 Ok(v) => v.clone(),
261 Err(e) => std::panic::resume_unwind(Box::new(e.to_string())),
262 };
263 Semigroup::append(x_val, y_val)
264 });
265 Lazy::new(thunk)
266 }
267}
268
269impl<A> LazyMonoid<A> for RcLazyConfig {
270 /// Returns the identity element.
271 ///
272 /// This method returns a lazy value that evaluates to the underlying type's identity element.
273 ///
274 /// ### Type Signature
275 ///
276 /// `forall a. Monoid a => () -> RcLazy a`
277 ///
278 /// ### Returns
279 ///
280 /// A lazy value containing the identity element.
281 ///
282 /// ### Examples
283 ///
284 /// ```
285 /// use fp_library::types::lazy::*;
286 ///
287 /// let x: ArcLazy<String> = ArcLazyConfig::empty();
288 /// assert_eq!(Lazy::force_or_panic(&x), "".to_string());
289 /// ```
290 fn empty<'a>() -> Lazy<'a, Self, A>
291 where
292 A: Monoid + Clone + 'a,
293 {
294 let thunk = Self::new_thunk(move |_| Monoid::empty());
295 Lazy::new(thunk)
296 }
297}
298
299impl<'a, A> LazyDefer<'a, A> for RcLazyConfig {
300 /// Creates a value from a computation that produces the value.
301 ///
302 /// This method defers the construction of a `Lazy` value.
303 ///
304 /// ### Type Signature
305 ///
306 /// `forall a. (() -> RcLazy a) -> RcLazy a`
307 ///
308 /// ### Type Parameters
309 ///
310 /// * `FnBrand`: The brand of the cloneable function wrapper.
311 ///
312 /// ### Parameters
313 ///
314 /// * `f`: A thunk (wrapped in a cloneable function) that produces the value.
315 ///
316 /// ### Returns
317 ///
318 /// A new lazy value.
319 ///
320 /// ### Examples
321 ///
322 /// ```
323 /// use fp_library::{brands::*, functions::*, types::lazy::*};
324 ///
325 /// let lazy = RcLazyConfig::defer::<RcFnBrand>(
326 /// cloneable_fn_new::<RcFnBrand, _, _>(|_| RcLazy::new(RcLazyConfig::new_thunk(|_| 42)))
327 /// );
328 /// assert_eq!(Lazy::force_or_panic(&lazy), 42);
329 /// ```
330 fn defer<FnBrand>(
331 f: <FnBrand as CloneableFn>::Of<'a, (), Lazy<'a, Self, A>>
332 ) -> Lazy<'a, Self, A>
333 where
334 FnBrand: CloneableFn + 'a,
335 A: Clone + 'a,
336 {
337 let thunk = Self::new_thunk(move |_| {
338 let inner_lazy = f(());
339 match Lazy::force(&inner_lazy) {
340 Ok(v) => v.clone(),
341 Err(e) => std::panic::resume_unwind(Box::new(e.to_string())),
342 }
343 });
344 Lazy::new(thunk)
345 }
346}
347
348// =============================================================================
349// ArcLazyConfig - Thread-safe lazy values
350// =============================================================================
351
352/// Configuration for `Arc`-based `Lazy` values.
353///
354/// Uses `Arc` for shared ownership, `OnceLock` for memoization, and thread-safe `ArcFn` for thunks.
355/// This configuration is **thread-safe** and requires `A: Send + Sync` for full functionality.
356///
357/// ### Thread Safety
358///
359/// `ArcLazy<A>` is `Send + Sync` when `A: Send + Sync`. This allows lazy values
360/// to be shared across threads safely.
361///
362/// ### Examples
363///
364/// ```
365/// use fp_library::types::lazy::*;
366/// use std::thread;
367///
368/// let lazy = ArcLazy::new(ArcLazyConfig::new_thunk(|_| 42));
369/// let lazy_clone = lazy.clone();
370///
371/// let handle = thread::spawn(move || {
372/// Lazy::force_or_panic(&lazy_clone)
373/// });
374///
375/// assert_eq!(handle.join().unwrap(), 42);
376/// ```
377#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
378pub struct ArcLazyConfig;
379
380impl ArcLazyConfig {
381 /// Creates a new thread-safe thunk from a closure.
382 ///
383 /// The closure must be `Send + Sync` to ensure thread safety.
384 ///
385 /// ### Type Signature
386 ///
387 /// `forall a. (Fn(()) -> a + Send + Sync) -> ThunkOf a`
388 ///
389 /// ### Type Parameters
390 ///
391 /// * `A`: The return type of the closure.
392 /// * `F`: The closure type (must be `Send + Sync`).
393 ///
394 /// ### Parameters
395 ///
396 /// * `f`: The closure to wrap.
397 ///
398 /// ### Returns
399 ///
400 /// A new thread-safe thunk.
401 ///
402 /// ### Examples
403 ///
404 /// ```
405 /// use fp_library::types::lazy::*;
406 ///
407 /// let thunk = ArcLazyConfig::new_thunk(|_| 42);
408 /// assert_eq!(thunk(()), 42);
409 /// ```
410 pub fn new_thunk<'a, A, F>(f: F) -> <Self as LazyConfig>::ThunkOf<'a, A>
411 where
412 A: 'a,
413 F: Fn(()) -> A + Send + Sync + 'a,
414 {
415 <ArcFnBrand as SendCloneableFn>::send_cloneable_fn_new(f)
416 }
417}
418
419impl LazyConfig for ArcLazyConfig {
420 type PtrBrand = ArcBrand;
421 type OnceBrand = OnceLockBrand;
422 type FnBrand = ArcFnBrand;
423 // Use SendOf for thread-safe thunks
424 type ThunkOf<'a, A>
425 = <ArcFnBrand as SendCloneableFn>::SendOf<'a, (), A>
426 where
427 A: 'a;
428}
429
430// LazySemigroup for ArcLazyConfig requires A: Send + Sync because:
431// 1. The closure captures Lazy values which must be Send + Sync to be in a Send + Sync closure
432// 2. The result A is stored in OnceLock which requires Send + Sync for thread-safe access
433impl<A: Send + Sync> LazySemigroup<A> for ArcLazyConfig {
434 /// Combines two lazy values.
435 ///
436 /// The combination is itself lazy: the result is a new thunk that, when forced,
437 /// forces both input values and combines them.
438 ///
439 /// ### Type Signature
440 ///
441 /// `forall a. (Semigroup a, Send a, Sync a) => (ArcLazy a, ArcLazy a) -> ArcLazy a`
442 ///
443 /// ### Parameters
444 ///
445 /// * `x`: The first lazy value.
446 /// * `y`: The second lazy value.
447 ///
448 /// ### Returns
449 ///
450 /// A new lazy value that combines the results.
451 ///
452 /// ### Examples
453 ///
454 /// ```
455 /// use fp_library::types::lazy::*;
456 ///
457 /// let x = RcLazy::new(RcLazyConfig::new_thunk(|_| "Hello, ".to_string()));
458 /// let y = RcLazy::new(RcLazyConfig::new_thunk(|_| "World!".to_string()));
459 /// let z = RcLazyConfig::append(x, y);
460 /// assert_eq!(Lazy::force_or_panic(&z), "Hello, World!".to_string());
461 /// ```
462 fn append<'a>(
463 x: Lazy<'a, Self, A>,
464 y: Lazy<'a, Self, A>,
465 ) -> Lazy<'a, Self, A>
466 where
467 A: Semigroup + Clone + 'a,
468 {
469 let thunk = Self::new_thunk(move |_| {
470 let x_val = match Lazy::force(&x) {
471 Ok(v) => v.clone(),
472 Err(e) => std::panic::resume_unwind(Box::new(e.to_string())),
473 };
474 let y_val = match Lazy::force(&y) {
475 Ok(v) => v.clone(),
476 Err(e) => std::panic::resume_unwind(Box::new(e.to_string())),
477 };
478 Semigroup::append(x_val, y_val)
479 });
480 Lazy::new(thunk)
481 }
482}
483
484impl<A: Send + Sync> LazyMonoid<A> for ArcLazyConfig {
485 /// Returns the identity element.
486 ///
487 /// This method returns a lazy value that evaluates to the underlying type's identity element.
488 ///
489 /// ### Type Signature
490 ///
491 /// `forall a. (Monoid a, Send a, Sync a) => () -> ArcLazy a`
492 ///
493 /// ### Returns
494 ///
495 /// A lazy value containing the identity element.
496 ///
497 /// ### Examples
498 ///
499 /// ```
500 /// use fp_library::types::lazy::*;
501 ///
502 /// let x: RcLazy<String> = RcLazyConfig::empty();
503 /// assert_eq!(Lazy::force_or_panic(&x), "".to_string());
504 /// ```
505 fn empty<'a>() -> Lazy<'a, Self, A>
506 where
507 A: Monoid + Clone + 'a,
508 {
509 let thunk = Self::new_thunk(move |_| Monoid::empty());
510 Lazy::new(thunk)
511 }
512}
513
514// Note: LazyDefer is NOT implemented for ArcLazyConfig because the Defer trait
515// allows any FnBrand, but ArcLazy requires Send + Sync closures. Users should
516// use SendDefer instead for thread-safe deferred lazy evaluation.
517
518// =============================================================================
519// Type Aliases
520// =============================================================================
521
522/// Type alias for `Rc`-based `Lazy` values.
523///
524/// Use this for single-threaded lazy evaluation. Not thread-safe.
525pub type RcLazy<'a, A> = Lazy<'a, RcLazyConfig, A>;
526
527/// Type alias for `Arc`-based `Lazy` values.
528///
529/// Use this for thread-safe lazy evaluation. Requires `A: Send + Sync`.
530pub type ArcLazy<'a, A> = Lazy<'a, ArcLazyConfig, A>;
531
532// =============================================================================
533// LazyError
534// =============================================================================
535
536/// Error type for `Lazy` evaluation failures.
537///
538/// This error is returned when a thunk panics during evaluation.
539#[derive(Clone, Debug, Default, Error, PartialEq, Eq, PartialOrd, Ord, Hash)]
540#[error("thunk panicked during evaluation{}", .0.as_ref().map(|m| format!(": {m}")).unwrap_or_default())]
541pub struct LazyError(Option<Arc<str>>);
542
543impl LazyError {
544 /// Creates a `LazyError` from a panic payload.
545 ///
546 /// ### Type Signature
547 ///
548 /// `Box (dyn Any + Send) -> LazyError`
549 ///
550 /// ### Parameters
551 ///
552 /// * `payload`: The panic payload.
553 ///
554 /// ### Returns
555 ///
556 /// A new `LazyError`.
557 ///
558 /// ### Examples
559 ///
560 /// ```
561 /// use fp_library::types::*;
562 ///
563 /// let payload = Box::new("oops");
564 /// let error = LazyError::from_panic(payload);
565 /// assert_eq!(error.to_string(), "thunk panicked during evaluation: oops");
566 /// ```
567 pub fn from_panic(payload: Box<dyn std::any::Any + Send + 'static>) -> Self {
568 let msg = if let Some(s) = payload.downcast_ref::<&str>() {
569 Some(Arc::from(*s))
570 } else {
571 payload.downcast_ref::<String>().map(|s| Arc::from(s.as_str()))
572 };
573 Self(msg)
574 }
575
576 /// Returns the panic message, if available.
577 ///
578 /// ### Type Signature
579 ///
580 /// `LazyError -> Option &str`
581 ///
582 /// ### Returns
583 ///
584 /// The panic message as a string slice, or `None` if no message was captured.
585 ///
586 /// ### Examples
587 ///
588 /// ```
589 /// use fp_library::types::*;
590 ///
591 /// let payload = Box::new("oops");
592 /// let error = LazyError::from_panic(payload);
593 /// assert_eq!(error.panic_message(), Some("oops"));
594 /// ```
595 pub fn panic_message(&self) -> Option<&str> {
596 self.0.as_deref()
597 }
598}
599
600// =============================================================================
601// LazyInner and Lazy
602// =============================================================================
603
604struct LazyInner<'a, Config: LazyConfig, A: 'a> {
605 /// The memoized result (computed at most once).
606 /// Stores Result<A, Arc<LazyError>> to capture both successful values and errors.
607 once: <Config::OnceBrand as Once>::Of<Result<A, Arc<LazyError>>>,
608 /// The thunk, wrapped in ThunkWrapper::Cell for interior mutability.
609 thunk: <Config::PtrBrand as ThunkWrapper>::Cell<Config::ThunkOf<'a, A>>,
610}
611
612/// Represents a lazily-computed, memoized value with shared semantics.
613///
614/// `Lazy` stores a computation (a thunk) that is executed only when the value is needed.
615/// The result is then cached (memoized) so that subsequent accesses return the same value
616/// without re-executing the computation.
617///
618/// This `Lazy` type uses shared semantics: cloning the `Lazy` value shares the
619/// underlying memoization state. If one clone forces the value, all other
620/// clones see the result.
621///
622/// ### Type Parameters
623///
624/// * `Config`: The configuration for the `Lazy` value (e.g., `RcLazyConfig`, `ArcLazyConfig`).
625/// * `A`: The type of the value.
626///
627/// ### Configuration Choice
628///
629/// - Use [`RcLazy`] for single-threaded contexts
630/// - Use [`ArcLazy`] for thread-safe contexts (requires `A: Send + Sync`)
631pub struct Lazy<'a, Config: LazyConfig, A>(
632 // CloneableOf wraps LazyInner for shared ownership
633 <Config::PtrBrand as RefCountedPointer>::CloneableOf<LazyInner<'a, Config, A>>,
634);
635
636impl<'a, Config: LazyConfig, A> Lazy<'a, Config, A> {
637 /// Creates a new `Lazy` value from a thunk.
638 ///
639 /// This method creates a new `Lazy` value that will evaluate the given thunk when forced.
640 ///
641 /// ### Type Signature
642 ///
643 /// `forall config a. config::ThunkOf a -> Lazy config a`
644 ///
645 /// ### Parameters
646 ///
647 /// * `thunk`: The thunk that produces the value.
648 ///
649 /// ### Returns
650 ///
651 /// A new `Lazy` value.
652 ///
653 /// ### Examples
654 ///
655 /// ```
656 /// use fp_library::types::lazy::*;
657 ///
658 /// let lazy = RcLazy::new(RcLazyConfig::new_thunk(|_| 42));
659 /// assert_eq!(Lazy::force(&lazy).unwrap(), &42);
660 /// ```
661 pub fn new(thunk: Config::ThunkOf<'a, A>) -> Self {
662 let inner =
663 LazyInner { once: Config::OnceBrand::new(), thunk: Config::PtrBrand::new(Some(thunk)) };
664 Self(Config::PtrBrand::cloneable_new(inner))
665 }
666
667 /// Forces the evaluation of the thunk and returns the value.
668 ///
669 /// If the value has already been computed, the cached value is returned.
670 /// If the computation panics, the panic is caught and returned as a `LazyError`.
671 /// Subsequent calls will return the same error.
672 ///
673 /// ### Type Signature
674 ///
675 /// `forall config a. Lazy config a -> Result (&a) LazyError`
676 ///
677 /// ### Parameters
678 ///
679 /// * `this`: The lazy value to force.
680 ///
681 /// ### Returns
682 ///
683 /// The computed value or an error.
684 ///
685 /// ### Examples
686 ///
687 /// ```
688 /// use fp_library::types::lazy::*;
689 ///
690 /// let lazy = RcLazy::new(RcLazyConfig::new_thunk(|_| 42));
691 /// assert_eq!(Lazy::force(&lazy).unwrap(), &42);
692 /// ```
693 pub fn force(this: &Self) -> Result<&A, LazyError> {
694 let inner = &*this.0;
695 let result: &Result<A, Arc<LazyError>> =
696 <Config::OnceBrand as Once>::get_or_init(&inner.once, || {
697 let thunk = Config::PtrBrand::take(&inner.thunk)
698 .expect("unreachable: get_or_init guarantees single execution");
699 // Call thunk with () since it's Fn(()) -> A
700 std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| thunk(())))
701 .map_err(|payload| Arc::new(LazyError::from_panic(payload)))
702 });
703 result.as_ref().map_err(|e| (**e).clone())
704 }
705
706 /// Forces the evaluation of the thunk and returns the value, cloning it.
707 ///
708 /// This is a convenience method that clones the value after forcing it.
709 ///
710 /// ### Type Signature
711 ///
712 /// `forall config a. Clone a => Lazy config a -> Result a LazyError`
713 ///
714 /// ### Parameters
715 ///
716 /// * `this`: The lazy value to force.
717 ///
718 /// ### Returns
719 ///
720 /// The computed value (cloned) or an error.
721 ///
722 /// ### Examples
723 ///
724 /// ```
725 /// use fp_library::types::lazy::*;
726 ///
727 /// let lazy = RcLazy::new(RcLazyConfig::new_thunk(|_| 42));
728 /// assert_eq!(Lazy::force_cloned(&lazy).unwrap(), 42);
729 /// ```
730 pub fn force_cloned(this: &Self) -> Result<A, LazyError>
731 where
732 A: Clone,
733 {
734 Self::force(this).cloned()
735 }
736
737 /// Forces the evaluation of the thunk and returns the value, panicking if the thunk panics.
738 ///
739 /// This method unwraps the result of `force`, propagating any panic that occurred during evaluation.
740 ///
741 /// ### Type Signature
742 ///
743 /// `forall config a. Clone a => Lazy config a -> a`
744 ///
745 /// ### Parameters
746 ///
747 /// * `this`: The lazy value to force.
748 ///
749 /// ### Returns
750 ///
751 /// The computed value.
752 ///
753 /// ### Examples
754 ///
755 /// ```
756 /// use fp_library::types::lazy::*;
757 ///
758 /// let lazy = RcLazy::new(RcLazyConfig::new_thunk(|_| 42));
759 /// assert_eq!(Lazy::force_or_panic(&lazy), 42);
760 /// ```
761 pub fn force_or_panic(this: &Self) -> A
762 where
763 A: Clone,
764 {
765 match Self::force(this) {
766 Ok(v) => v.clone(),
767 Err(e) => std::panic::resume_unwind(Box::new(e.to_string())),
768 }
769 }
770
771 /// Forces the evaluation of the thunk and returns a reference to the value, panicking if the thunk panics.
772 ///
773 /// This method unwraps the result of `force`, propagating any panic that occurred during evaluation.
774 ///
775 /// ### Type Signature
776 ///
777 /// `forall config a. Lazy config a -> &a`
778 ///
779 /// ### Parameters
780 ///
781 /// * `this`: The lazy value to force.
782 ///
783 /// ### Returns
784 ///
785 /// A reference to the computed value.
786 ///
787 /// ### Examples
788 ///
789 /// ```
790 /// use fp_library::types::lazy::*;
791 ///
792 /// let lazy = RcLazy::new(RcLazyConfig::new_thunk(|_| 42));
793 /// assert_eq!(Lazy::force_ref_or_panic(&lazy), &42);
794 /// ```
795 pub fn force_ref_or_panic(this: &Self) -> &A {
796 match Self::force(this) {
797 Ok(v) => v,
798 Err(e) => std::panic::resume_unwind(Box::new(e.to_string())),
799 }
800 }
801
802 /// Returns true if the lazy value has been forced and panicked.
803 ///
804 /// ### Type Signature
805 ///
806 /// `forall config a. Lazy config a -> bool`
807 ///
808 /// ### Parameters
809 ///
810 /// * `this`: The lazy value to check.
811 ///
812 /// ### Returns
813 ///
814 /// `true` if the value has been forced and panicked, `false` otherwise.
815 ///
816 /// ### Examples
817 ///
818 /// ```
819 /// use fp_library::types::lazy::*;
820 ///
821 /// let lazy = RcLazy::new(RcLazyConfig::new_thunk(|_| panic!("oops")));
822 /// let _ = Lazy::force(&lazy);
823 /// assert!(Lazy::is_poisoned(&lazy));
824 /// ```
825 pub fn is_poisoned(this: &Self) -> bool {
826 let inner = &*this.0;
827 if let Some(result) = <Config::OnceBrand as Once>::get(&inner.once) {
828 result.is_err()
829 } else {
830 false
831 }
832 }
833
834 /// Returns the error if the lazy value has been forced and panicked.
835 ///
836 /// ### Type Signature
837 ///
838 /// `forall config a. Lazy config a -> Option LazyError`
839 ///
840 /// ### Parameters
841 ///
842 /// * `this`: The lazy value to check.
843 ///
844 /// ### Returns
845 ///
846 /// The error if the value has been forced and panicked, `None` otherwise.
847 ///
848 /// ### Examples
849 ///
850 /// ```
851 /// use fp_library::types::lazy::*;
852 ///
853 /// let lazy = RcLazy::new(RcLazyConfig::new_thunk(|_| panic!("oops")));
854 /// let _ = Lazy::force(&lazy);
855 /// assert!(Lazy::get_error(&lazy).is_some());
856 /// ```
857 pub fn get_error(this: &Self) -> Option<LazyError> {
858 let inner = &*this.0;
859 if let Some(Err(e)) = <Config::OnceBrand as Once>::get(&inner.once) {
860 Some(LazyError(e.0.clone()))
861 } else {
862 None
863 }
864 }
865}
866
867impl<'a, Config: LazyConfig, A: Debug> Debug for Lazy<'a, Config, A> {
868 fn fmt(
869 &self,
870 f: &mut Formatter<'_>,
871 ) -> fmt::Result {
872 let inner = &*self.0;
873 f.debug_struct("Lazy")
874 .field("value", &<Config::OnceBrand as Once>::get(&inner.once))
875 .finish()
876 }
877}
878
879impl<'a, Config: LazyConfig, A: Clone> Clone for Lazy<'a, Config, A> {
880 fn clone(&self) -> Self {
881 Self(self.0.clone())
882 }
883}
884
885impl_kind! {
886 impl<Config: LazyConfig> for LazyBrand<Config> {
887 type Of<'a, A: 'a>: 'a = Lazy<'a, Config, A>;
888 }
889}
890
891// =============================================================================
892// Type Class Implementations
893// =============================================================================
894
895// Note: We do NOT implement TrySemigroup/TryMonoid explicitly for Lazy.
896// Since Lazy implements Semigroup/Monoid, it inherits the blanket impls from
897// TrySemigroup/TryMonoid which use Error = Infallible. Users should handle
898// errors via force() returning Result<&A, LazyError>.
899
900impl<'a, Config: LazySemigroup<A>, A: Semigroup + Clone + 'a> Semigroup for Lazy<'a, Config, A> {
901 /// Combines two lazy values.
902 ///
903 /// The combination is itself lazy: the result is a new thunk that, when forced,
904 /// forces both input values and combines them.
905 ///
906 /// ### Type Signature
907 ///
908 /// `forall config a. Semigroup a => (Lazy config a, Lazy config a) -> Lazy config a`
909 ///
910 /// ### Parameters
911 ///
912 /// * `x`: The first lazy value.
913 /// * `y`: The second lazy value.
914 ///
915 /// ### Returns
916 ///
917 /// A new lazy value that combines the results.
918 ///
919 /// ### Examples
920 ///
921 /// ```
922 /// use fp_library::{functions::*, types::*};
923 ///
924 /// let x = RcLazy::new(RcLazyConfig::new_thunk(|_| "Hello, ".to_string()));
925 /// let y = RcLazy::new(RcLazyConfig::new_thunk(|_| "World!".to_string()));
926 /// let z = append::<RcLazy<_>>(x, y);
927 /// assert_eq!(Lazy::force_or_panic(&z), "Hello, World!".to_string());
928 /// ```
929 fn append(
930 x: Self,
931 y: Self,
932 ) -> Self {
933 Config::append(x, y)
934 }
935}
936
937impl<'a, Config: LazyMonoid<A>, A: Monoid + Clone + 'a> Monoid for Lazy<'a, Config, A> {
938 /// Returns the identity element.
939 ///
940 /// This method returns a lazy value that evaluates to the underlying type's identity element.
941 ///
942 /// ### Type Signature
943 ///
944 /// `forall config a. Monoid a => () -> Lazy config a`
945 ///
946 /// ### Returns
947 ///
948 /// A lazy value containing the identity element.
949 ///
950 /// ### Examples
951 ///
952 /// ```
953 /// use fp_library::{functions::*, types::*};
954 ///
955 /// let x = empty::<RcLazy<String>>();
956 /// assert_eq!(Lazy::force_or_panic(&x), "".to_string());
957 /// ```
958 fn empty() -> Self {
959 Config::empty()
960 }
961}
962
963impl<'a, Config: LazyDefer<'a, A>, A: Clone + 'a> Defer<'a> for Lazy<'a, Config, A> {
964 /// Creates a value from a computation that produces the value.
965 ///
966 /// This method defers the construction of a `Lazy` value.
967 ///
968 /// ### Type Signature
969 ///
970 /// `forall config a. (() -> Lazy config a) -> Lazy config a`
971 ///
972 /// ### Type Parameters
973 ///
974 /// * `FnBrand`: The brand of the cloneable function wrapper.
975 ///
976 /// ### Parameters
977 ///
978 /// * `f`: A thunk (wrapped in a cloneable function) that produces the value.
979 ///
980 /// ### Returns
981 ///
982 /// A new lazy value.
983 ///
984 /// ### Examples
985 ///
986 /// ```
987 /// use fp_library::{brands::*, functions::*, types::lazy::*};
988 ///
989 /// let lazy = defer::<RcLazy<i32>, RcFnBrand>(
990 /// cloneable_fn_new::<RcFnBrand, _, _>(|_| RcLazy::new(RcLazyConfig::new_thunk(|_| 42)))
991 /// );
992 /// assert_eq!(Lazy::force_or_panic(&lazy), 42);
993 /// ```
994 fn defer<FnBrand>(f: <FnBrand as CloneableFn>::Of<'a, (), Self>) -> Self
995 where
996 Self: Sized,
997 FnBrand: CloneableFn + 'a,
998 {
999 Config::defer::<FnBrand>(f)
1000 }
1001}
1002
1003use crate::classes::send_defer::SendDefer;
1004
1005impl SendDefer for LazyBrand<ArcLazyConfig> {
1006 /// Creates a deferred value from a thread-safe thunk.
1007 ///
1008 /// ### Type Signature
1009 ///
1010 /// `forall a. (Send a, Sync a) => (() -> ArcLazy a) -> ArcLazy a`
1011 ///
1012 /// ### Type Parameters
1013 ///
1014 /// * `A`: The type of the value.
1015 ///
1016 /// ### Parameters
1017 ///
1018 /// * `thunk`: The function that produces the value.
1019 ///
1020 /// ### Returns
1021 ///
1022 /// A deferred value.
1023 ///
1024 /// ### Examples
1025 ///
1026 /// ```
1027 /// use fp_library::{brands::*, functions::*, types::lazy::*};
1028 ///
1029 /// let lazy = send_defer::<LazyBrand<ArcLazyConfig>, _, _>(|| ArcLazy::new(ArcLazyConfig::new_thunk(|_| 42)));
1030 /// assert_eq!(Lazy::force_or_panic(&lazy), 42);
1031 /// ```
1032 fn send_defer<'a, A>(thunk: impl 'a + Fn() -> Self::Of<'a, A> + Send + Sync) -> Self::Of<'a, A>
1033 where
1034 A: Clone + Send + Sync + 'a,
1035 {
1036 let thunk = ArcLazyConfig::new_thunk(move |_| {
1037 let inner_lazy = thunk();
1038 match Lazy::force(&inner_lazy) {
1039 Ok(v) => v.clone(),
1040 Err(e) => std::panic::resume_unwind(Box::new(e.to_string())),
1041 }
1042 });
1043 Lazy::new(thunk)
1044 }
1045}
1046
1047// =============================================================================
1048// Tests
1049// =============================================================================
1050
1051#[cfg(test)]
1052mod tests {
1053 use super::*;
1054 use crate::{
1055 brands::RcFnBrand,
1056 classes::{cloneable_fn::CloneableFn, defer::Defer},
1057 };
1058 use std::{cell::RefCell, rc::Rc};
1059
1060 /// Tests that `Lazy::force` memoizes the result.
1061 #[test]
1062 fn force_memoization() {
1063 let counter = Rc::new(RefCell::new(0));
1064 let counter_clone = counter.clone();
1065
1066 let lazy = RcLazy::new(<RcFnBrand as CloneableFn>::new(move |_| {
1067 *counter_clone.borrow_mut() += 1;
1068 42
1069 }));
1070
1071 assert_eq!(*counter.borrow(), 0);
1072 assert_eq!(Lazy::force(&lazy).unwrap(), &42);
1073 assert_eq!(*counter.borrow(), 1);
1074 assert_eq!(Lazy::force(&lazy).unwrap(), &42);
1075 // The new implementation uses shared semantics!
1076 // So cloning the Lazy should SHARE the OnceCell.
1077 let lazy_clone = lazy.clone();
1078 assert_eq!(Lazy::force(&lazy_clone).unwrap(), &42);
1079 assert_eq!(*counter.borrow(), 1); // Should still be 1
1080 }
1081
1082 /// Tests that `Lazy::defer` delays execution until forced.
1083 #[test]
1084 fn defer_execution_order() {
1085 let counter = Rc::new(RefCell::new(0));
1086 let counter_clone = counter.clone();
1087
1088 let lazy = RcLazy::defer::<RcFnBrand>(<RcFnBrand as CloneableFn>::new(move |_| {
1089 *counter_clone.borrow_mut() += 1;
1090 RcLazy::new(<RcFnBrand as CloneableFn>::new(|_| 42))
1091 }));
1092
1093 assert_eq!(*counter.borrow(), 0);
1094 assert_eq!(Lazy::force(&lazy).unwrap(), &42);
1095 assert_eq!(*counter.borrow(), 1);
1096 }
1097
1098 /// Tests that panics are caught and cached.
1099 #[test]
1100 fn panic_caching() {
1101 let counter = Rc::new(RefCell::new(0));
1102 let counter_clone = counter.clone();
1103
1104 let lazy = RcLazy::new(<RcFnBrand as CloneableFn>::new(move |_| {
1105 *counter_clone.borrow_mut() += 1;
1106 if *counter_clone.borrow() == 1 {
1107 panic!("oops");
1108 }
1109 42
1110 }));
1111
1112 assert!(Lazy::force(&lazy).is_err());
1113 assert_eq!(*counter.borrow(), 1);
1114 assert!(Lazy::force(&lazy).is_err());
1115 assert_eq!(*counter.borrow(), 1); // Should not re-execute
1116 }
1117
1118 /// Tests that `force_or_panic` returns the value on success.
1119 #[test]
1120 fn force_or_panic_success() {
1121 let lazy = RcLazy::new(RcLazyConfig::new_thunk(|_| 42));
1122 assert_eq!(Lazy::force_or_panic(&lazy), 42);
1123 }
1124
1125 /// Tests that `force_or_panic` propagates the panic on failure.
1126 #[test]
1127 #[should_panic(expected = "thunk panicked during evaluation: oops")]
1128 fn force_or_panic_failure() {
1129 let lazy = RcLazy::new(RcLazyConfig::new_thunk(|_| panic!("oops")));
1130 Lazy::force_or_panic(&lazy);
1131 }
1132
1133 /// Tests that `force_ref_or_panic` returns a reference to the value on success.
1134 #[test]
1135 fn force_ref_or_panic_success() {
1136 let lazy = RcLazy::new(RcLazyConfig::new_thunk(|_| 42));
1137 assert_eq!(Lazy::force_ref_or_panic(&lazy), &42);
1138 }
1139
1140 /// Tests that `force_ref_or_panic` propagates the panic on failure.
1141 #[test]
1142 #[should_panic(expected = "thunk panicked during evaluation: oops")]
1143 fn force_ref_or_panic_failure() {
1144 let lazy = RcLazy::new(RcLazyConfig::new_thunk(|_| panic!("oops")));
1145 Lazy::force_ref_or_panic(&lazy);
1146 }
1147
1148 /// Tests `is_poisoned` and `get_error` state transitions.
1149 #[test]
1150 fn is_poisoned_and_get_error() {
1151 let lazy = RcLazy::new(RcLazyConfig::new_thunk(|_| panic!("oops")));
1152 assert!(!Lazy::is_poisoned(&lazy));
1153 assert!(Lazy::get_error(&lazy).is_none());
1154
1155 let _ = Lazy::force(&lazy);
1156
1157 assert!(Lazy::is_poisoned(&lazy));
1158 let err = Lazy::get_error(&lazy).unwrap();
1159 assert_eq!(err.to_string(), "thunk panicked during evaluation: oops");
1160 }
1161
1162 /// Tests that `force_cloned` returns a cloned value.
1163 #[test]
1164 fn force_cloned() {
1165 let lazy = RcLazy::new(RcLazyConfig::new_thunk(|_| 42));
1166 assert_eq!(Lazy::force_cloned(&lazy).unwrap(), 42);
1167 }
1168
1169 /// Tests `Semigroup::append` for `Lazy`.
1170 #[test]
1171 fn semigroup_append() {
1172 let x = RcLazy::new(RcLazyConfig::new_thunk(|_| "Hello, ".to_string()));
1173 let y = RcLazy::new(RcLazyConfig::new_thunk(|_| "World!".to_string()));
1174 let z = Semigroup::append(x, y);
1175 assert_eq!(Lazy::force_or_panic(&z), "Hello, World!".to_string());
1176 }
1177
1178 /// Tests `Monoid::empty` for `Lazy`.
1179 #[test]
1180 fn monoid_empty() {
1181 let x = <RcLazy<String> as Monoid>::empty();
1182 assert_eq!(Lazy::force_or_panic(&x), "".to_string());
1183 }
1184
1185 /// Tests that `ArcLazy` is thread-safe.
1186 #[test]
1187 fn arc_lazy_thread_safety() {
1188 use std::sync::{Arc, Mutex};
1189 use std::thread;
1190
1191 let counter = Arc::new(Mutex::new(0));
1192 let counter_clone = counter.clone();
1193
1194 let lazy = ArcLazy::new(ArcLazyConfig::new_thunk(move |_| {
1195 let mut guard = counter_clone.lock().unwrap();
1196 *guard += 1;
1197 42
1198 }));
1199
1200 let lazy_clone = lazy.clone();
1201
1202 let handle = thread::spawn(move || Lazy::force_or_panic(&lazy_clone));
1203
1204 assert_eq!(handle.join().unwrap(), 42);
1205 assert_eq!(Lazy::force_or_panic(&lazy), 42);
1206 // Should only be computed once due to shared memoization
1207 assert_eq!(*counter.lock().unwrap(), 1);
1208 }
1209
1210 /// Tests `Semigroup::append` for `ArcLazy`.
1211 #[test]
1212 fn arc_lazy_semigroup_append() {
1213 let x = ArcLazy::new(ArcLazyConfig::new_thunk(|_| "Hello, ".to_string()));
1214 let y = ArcLazy::new(ArcLazyConfig::new_thunk(|_| "World!".to_string()));
1215 let z = Semigroup::append(x, y);
1216 assert_eq!(Lazy::force_or_panic(&z), "Hello, World!".to_string());
1217 }
1218
1219 /// Tests `Monoid::empty` for `ArcLazy`.
1220 #[test]
1221 fn arc_lazy_monoid_empty() {
1222 let x = <ArcLazy<String> as Monoid>::empty();
1223 assert_eq!(Lazy::force_or_panic(&x), "".to_string());
1224 }
1225}