Skip to main content

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}