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