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
6use crate::{
7	brands::LazyBrand,
8	classes::{
9		clonable_fn::ClonableFn, defer::Defer, monoid::Monoid, once::Once, semigroup::Semigroup,
10	},
11	impl_kind,
12	kinds::*,
13};
14
15/// Represents a lazily-computed, memoized value.
16///
17/// `Lazy` stores a computation (a thunk) that is executed only when the value is needed.
18/// The result is then cached (memoized) so that subsequent accesses return the same value
19/// without re-executing the computation.
20pub struct Lazy<'a, OnceBrand: Once, FnBrand: ClonableFn, A>(
21	pub <OnceBrand as Once>::Of<A>,
22	pub <FnBrand as ClonableFn>::Of<'a, (), A>,
23);
24
25impl<'a, OnceBrand: Once, FnBrand: ClonableFn, A> Lazy<'a, OnceBrand, FnBrand, A> {
26	/// Creates a new `Lazy` value from a thunk.
27	///
28	/// The thunk is wrapped in a clonable function (e.g., `Rc<dyn Fn() -> A>`) to allow
29	/// the `Lazy` value to be cloned.
30	///
31	/// ### Type Signature
32	///
33	/// `forall a. (() -> a) -> Lazy a`
34	///
35	/// ### Type Parameters
36	///
37	/// * `OnceBrand`: The brand of the once cell (e.g., `OnceCellBrand`).
38	/// * `FnBrand`: The brand of the clonable function (e.g., `RcFnBrand`).
39	/// * `A`: The type of the value.
40	///
41	/// ### Parameters
42	///
43	/// * `a`: The thunk that produces the value.
44	///
45	/// ### Returns
46	///
47	/// A new `Lazy` value.
48	///
49	/// ### Examples
50	///
51	/// ```
52	/// use fp_library::types::lazy::Lazy;
53	/// use fp_library::brands::RcFnBrand;
54	/// use fp_library::brands::OnceCellBrand;
55	/// use fp_library::classes::clonable_fn::ClonableFn;
56	///
57	/// let lazy = Lazy::<OnceCellBrand, RcFnBrand, _>::new(<RcFnBrand as ClonableFn>::new(|_| 42));
58	/// ```
59	pub fn new(a: <FnBrand as ClonableFn>::Of<'a, (), A>) -> Self {
60		Self(OnceBrand::new(), a)
61	}
62
63	/// Forces the evaluation of the thunk and returns the value.
64	///
65	/// If the value has already been computed, the cached value is returned.
66	/// Requires `A: Clone` because the value is stored inside the `Lazy` struct and
67	/// must be cloned to be returned to the caller.
68	///
69	/// ### Type Signature
70	///
71	/// `forall a. Lazy a -> a`
72	///
73	/// ### Type Parameters
74	///
75	/// * `OnceBrand`: The brand of the once cell (e.g., `OnceCellBrand`).
76	/// * `FnBrand`: The brand of the clonable function (e.g., `RcFnBrand`).
77	/// * `A`: The type of the value.
78	///
79	/// ### Parameters
80	///
81	/// * `a`: The lazy value to force.
82	///
83	/// ### Returns
84	///
85	/// The computed value.
86	///
87	/// ### Examples
88	///
89	/// ```
90	/// use fp_library::types::lazy::Lazy;
91	/// use fp_library::brands::RcFnBrand;
92	/// use fp_library::brands::OnceCellBrand;
93	/// use fp_library::classes::clonable_fn::ClonableFn;
94	///
95	/// let lazy = Lazy::<OnceCellBrand, RcFnBrand, _>::new(<RcFnBrand as ClonableFn>::new(|_| 42));
96	/// assert_eq!(Lazy::force(lazy), 42);
97	/// ```
98	pub fn force(a: Self) -> A
99	where
100		A: Clone,
101	{
102		<OnceBrand as Once>::get_or_init(&a.0, move || (a.1)(())).clone()
103	}
104}
105
106impl<'a, OnceBrand: Once, FnBrand: ClonableFn, A: Clone> Clone for Lazy<'a, OnceBrand, FnBrand, A>
107where
108	<OnceBrand as Once>::Of<A>: Clone,
109{
110	fn clone(&self) -> Self {
111		Self(self.0.clone(), self.1.clone())
112	}
113}
114
115impl_kind! {
116	impl<OnceBrand: Once + 'static, FnBrand: ClonableFn + 'static>
117		for LazyBrand<OnceBrand, FnBrand>
118	{
119		type Of<'a, A: 'a>: 'a = Lazy<'a, OnceBrand, FnBrand, A>;
120	}
121}
122
123// Note: Lazy cannot implement Functor, Pointed, or Semimonad because these traits
124// require operations to work for all types A, but Lazy requires A: Clone to be
125// forced (memoized). Adding A: Clone bounds to the traits would restrict all
126// other implementations (e.g. Option<NonClone>), which is undesirable.
127//
128// Consequently, Lazy cannot implement Semiapplicative either, as it extends Functor.
129
130impl<'b, OnceBrand: 'b + Once, FnBrand: 'b + ClonableFn, A: Semigroup + Clone + 'b> Semigroup
131	for Lazy<'b, OnceBrand, FnBrand, A>
132where
133	<OnceBrand as Once>::Of<A>: Clone,
134{
135	/// The result of combining the two values using the semigroup operation.
136	///
137	/// This method combines two lazy values using the underlying type's `Semigroup` implementation.
138	/// The combination is itself lazy: the result is a new thunk that, when forced,
139	/// forces both input values and combines them.
140	///
141	/// ### Type Signature
142	///
143	/// `forall a. Semigroup a => (Lazy a, Lazy a) -> Lazy a`
144	///
145	/// ### Parameters
146	///
147	/// * `a`: The first lazy value.
148	/// * `b`: The second lazy value.
149	///
150	/// ### Returns
151	///
152	/// A new lazy value that combines the results.
153	///
154	/// ### Examples
155	///
156	/// ```
157	/// use fp_library::types::lazy::Lazy;
158	/// use fp_library::brands::RcFnBrand;
159	/// use fp_library::brands::OnceCellBrand;
160	/// use fp_library::classes::clonable_fn::ClonableFn;
161	/// use fp_library::classes::semigroup::Semigroup;
162	/// use fp_library::types::string; // Import Semigroup impl for String
163	///
164	/// let x = Lazy::<OnceCellBrand, RcFnBrand, _>::new(<RcFnBrand as ClonableFn>::new(|_| "Hello, ".to_string()));
165	/// let y = Lazy::<OnceCellBrand, RcFnBrand, _>::new(<RcFnBrand as ClonableFn>::new(|_| "World!".to_string()));
166	/// let z = Semigroup::append(x, y);
167	/// assert_eq!(Lazy::force(z), "Hello, World!".to_string());
168	/// ```
169	fn append(
170		a: Self,
171		b: Self,
172	) -> Self {
173		Lazy::new(<FnBrand as ClonableFn>::new(move |_| {
174			Semigroup::append(Lazy::force(a.clone()), Lazy::force(b.clone()))
175		}))
176	}
177}
178
179impl<'b, OnceBrand: 'b + Once, FnBrand: 'b + ClonableFn, A: Monoid + Clone + 'b> Monoid
180	for Lazy<'b, OnceBrand, FnBrand, A>
181where
182	<OnceBrand as Once>::Of<A>: Clone,
183{
184	/// The identity element.
185	///
186	/// This method returns a lazy value that evaluates to the underlying type's identity element.
187	///
188	/// ### Type Signature
189	///
190	/// `forall a. Monoid a => () -> Lazy a`
191	///
192	/// ### Returns
193	///
194	/// A lazy value containing the identity element.
195	///
196	/// ### Examples
197	///
198	/// ```
199	/// use fp_library::types::lazy::Lazy;
200	/// use fp_library::brands::RcFnBrand;
201	/// use fp_library::brands::OnceCellBrand;
202	/// use fp_library::classes::monoid::Monoid;
203	/// use fp_library::types::string; // Import Monoid impl for String
204	///
205	/// let x = Lazy::<OnceCellBrand, RcFnBrand, String>::empty();
206	/// assert_eq!(Lazy::force(x), "".to_string());
207	/// ```
208	fn empty() -> Self {
209		Lazy::new(<FnBrand as ClonableFn>::new(move |_| Monoid::empty()))
210	}
211}
212
213impl<'a, OnceBrand: Once + 'a, FnBrand: ClonableFn + 'a, A: Clone + 'a> Defer<'a>
214	for Lazy<'a, OnceBrand, FnBrand, A>
215{
216	/// Creates a value from a computation that produces the value.
217	///
218	/// This method defers the construction of a `Lazy` value.
219	/// This allows creating a `Lazy` value from a computation that produces a `Lazy` value.
220	/// The outer computation is executed only when the result is forced.
221	///
222	/// ### Type Signature
223	///
224	/// `forall a. (() -> Lazy a) -> Lazy a`
225	/// ### Type Parameters
226	///
227	/// * `FnBrand`: The brand of the clonable function wrapper.
228	///
229	/// ### Parameters
230	///
231	/// * `f`: A thunk (wrapped in a clonable function) that produces the value.
232	///
233	/// ### Returns
234	///
235	/// A new lazy value.
236	///
237	/// ### Examples
238	///
239	/// ```
240	/// use fp_library::types::lazy::Lazy;
241	/// use fp_library::brands::RcFnBrand;
242	/// use fp_library::brands::OnceCellBrand;
243	/// use fp_library::classes::clonable_fn::ClonableFn;
244	/// use fp_library::classes::defer::Defer;
245	/// use std::rc::Rc;
246	///
247	/// let lazy = Lazy::<OnceCellBrand, RcFnBrand, _>::defer::<RcFnBrand>(
248	///     <RcFnBrand as ClonableFn>::new(|_| Lazy::new(<RcFnBrand as ClonableFn>::new(|_| 42)))
249	/// );
250	/// assert_eq!(Lazy::force(lazy), 42);
251	/// ```
252	fn defer<FnBrand_>(f: <FnBrand_ as ClonableFn>::Of<'a, (), Self>) -> Self
253	where
254		Self: Sized,
255		FnBrand_: ClonableFn + 'a,
256	{
257		Self::new(<FnBrand as ClonableFn>::new(move |_| Lazy::force(f(()))))
258	}
259}
260
261#[cfg(test)]
262mod tests {
263	use super::*;
264	use crate::{
265		brands::{OnceCellBrand, RcFnBrand},
266		classes::{clonable_fn::ClonableFn, defer::Defer},
267	};
268	use std::{cell::RefCell, rc::Rc};
269
270	/// Tests that `Lazy::force` memoizes the result.
271	#[test]
272	fn force_memoization() {
273		let counter = Rc::new(RefCell::new(0));
274		let counter_clone = counter.clone();
275
276		let lazy =
277			Lazy::<OnceCellBrand, RcFnBrand, _>::new(<RcFnBrand as ClonableFn>::new(move |_| {
278				*counter_clone.borrow_mut() += 1;
279				42
280			}));
281
282		assert_eq!(*counter.borrow(), 0);
283		assert_eq!(Lazy::force(lazy.clone()), 42);
284		assert_eq!(*counter.borrow(), 1);
285		assert_eq!(Lazy::force(lazy), 42);
286		// Since we clone before forcing, and OnceCell is not shared across clones (it's deep cloned),
287		// the counter increments again.
288		assert_eq!(*counter.borrow(), 2);
289	}
290
291	/// Tests that `Lazy::defer` delays execution until forced.
292	#[test]
293	fn defer_execution_order() {
294		let counter = Rc::new(RefCell::new(0));
295		let counter_clone = counter.clone();
296
297		let lazy = Lazy::<OnceCellBrand, RcFnBrand, _>::defer::<RcFnBrand>(
298			<RcFnBrand as ClonableFn>::new(move |_| {
299				*counter_clone.borrow_mut() += 1;
300				Lazy::new(<RcFnBrand as ClonableFn>::new(|_| 42))
301			}),
302		);
303
304		assert_eq!(*counter.borrow(), 0);
305		assert_eq!(Lazy::force(lazy), 42);
306		assert_eq!(*counter.borrow(), 1);
307	}
308}