fp_library/classes/deferrable.rs
1//! Types that can be constructed lazily from a computation.
2//!
3//! ### Examples
4//!
5//! ```
6//! use fp_library::{
7//! functions::*,
8//! types::*,
9//! };
10//!
11//! let eval: Thunk<i32> = defer(|| Thunk::pure(42));
12//! assert_eq!(eval.evaluate(), 42);
13//! ```
14
15#[fp_macros::document_module]
16mod inner {
17 use fp_macros::*;
18 /// A type class for types that can be constructed lazily.
19 ///
20 /// `Deferrable` is the inverse of [`Extract`](crate::classes::Extract): where
21 /// `Extract` forces/extracts the inner value, `Deferrable` constructs a value
22 /// lazily from a thunk. For types whose brand implements `Extract` (e.g.,
23 /// [`ThunkBrand`](crate::brands::ThunkBrand)), `extract(defer(|| x)) == x`
24 /// forms a round-trip. Note that `Deferrable` is a value-level trait
25 /// (implemented by concrete types like `Thunk`), while `Extract` is a
26 /// brand-level trait (implemented by `ThunkBrand`).
27 ///
28 /// ### Laws
29 ///
30 /// `Deferrable` instances must satisfy the following law:
31 /// * Transparency: The value produced by `defer(|| x)` is identical to `x`. This law
32 /// does not constrain *when* evaluation occurs; some implementations may evaluate eagerly.
33 ///
34 /// ### Why there is no generic `fix`
35 ///
36 /// In PureScript, `fix :: Lazy l => (l -> l) -> l` enables lazy self-reference,
37 /// which is essential for tying the knot in recursive values. In Rust, lazy
38 /// self-reference requires shared ownership (`Rc`/`Arc`) and interior mutability,
39 /// which are properties specific to [`Lazy`](crate::types::Lazy) rather than
40 /// all `Deferrable` types. For example, [`Thunk`](crate::types::Thunk) is consumed
41 /// on evaluation, so self-referential construction is not possible.
42 ///
43 /// The concrete functions [`rc_lazy_fix`](crate::types::lazy::rc_lazy_fix) and
44 /// [`arc_lazy_fix`](crate::types::lazy::arc_lazy_fix) provide this capability for
45 /// `Lazy` specifically.
46 ///
47 /// # Warning
48 ///
49 /// Some implementations may evaluate the thunk eagerly when the produced type requires
50 /// `Send`. For example, `ArcLazy`'s `Deferrable` implementation evaluates the outer thunk
51 /// immediately because `ArcLazy::new` requires a `Send` closure, but the `Deferrable`
52 /// trait does not impose that bound. If you need guaranteed deferred evaluation with
53 /// thread-safe types, prefer [`SendDeferrable`](crate::classes::SendDeferrable) instead.
54 #[document_type_parameters("The lifetime of the computation.")]
55 #[document_examples]
56 ///
57 /// Transparency law for [`Thunk`](crate::types::Thunk):
58 ///
59 /// ```
60 /// use fp_library::{
61 /// functions::*,
62 /// types::*,
63 /// };
64 ///
65 /// // Transparency: defer(|| x) is equivalent to x when evaluated.
66 /// let x = Thunk::pure(42);
67 /// let deferred: Thunk<i32> = defer(|| Thunk::pure(42));
68 /// assert_eq!(deferred.evaluate(), x.evaluate());
69 /// ```
70 pub trait Deferrable<'a> {
71 /// Creates a value from a computation that produces the value.
72 ///
73 /// This function takes a thunk and creates a deferred value that will be computed using the thunk.
74 #[document_signature]
75 ///
76 #[document_parameters("A thunk that produces the value.")]
77 ///
78 #[document_returns("The deferred value.")]
79 #[document_examples]
80 ///
81 /// ```
82 /// use fp_library::{
83 /// functions::*,
84 /// types::*,
85 /// };
86 ///
87 /// let eval: Thunk<i32> = defer(|| Thunk::pure(42));
88 /// assert_eq!(eval.evaluate(), 42);
89 /// ```
90 fn defer(f: impl FnOnce() -> Self + 'a) -> Self
91 where
92 Self: Sized;
93 }
94
95 /// Creates a value from a computation that produces the value.
96 ///
97 /// Free function version that dispatches to [the type class' associated function][`Deferrable::defer`].
98 #[document_signature]
99 ///
100 #[document_type_parameters(
101 "The lifetime of the computation.",
102 "The type of the deferred value."
103 )]
104 ///
105 #[document_parameters("A thunk that produces the value.")]
106 ///
107 #[document_returns("The deferred value.")]
108 #[document_examples]
109 ///
110 /// ```
111 /// use fp_library::{
112 /// functions::*,
113 /// types::*,
114 /// };
115 ///
116 /// let eval: Thunk<i32> = defer(|| Thunk::pure(42));
117 /// assert_eq!(eval.evaluate(), 42);
118 /// ```
119 pub fn defer<'a, D: Deferrable<'a>>(f: impl FnOnce() -> D + 'a) -> D {
120 D::defer(f)
121 }
122}
123
124pub use inner::*;
125
126#[cfg(test)]
127mod tests {
128 use {
129 crate::{
130 functions::*,
131 types::*,
132 },
133 quickcheck_macros::quickcheck,
134 };
135
136 /// Deferrable transparency law: evaluate(defer(|| x)) == x.
137 #[quickcheck]
138 fn prop_deferrable_transparency(x: i32) -> bool {
139 let deferred: Thunk<i32> = defer(|| Thunk::pure(x));
140 deferred.evaluate() == x
141 }
142
143 /// Deferrable nesting law: evaluate(defer(|| defer(|| x))) == evaluate(defer(|| x)).
144 #[quickcheck]
145 fn prop_deferrable_nesting(x: i32) -> bool {
146 let nested: Thunk<i32> = defer(|| defer(|| Thunk::pure(x)));
147 let single: Thunk<i32> = defer(|| Thunk::pure(x));
148 nested.evaluate() == single.evaluate()
149 }
150}