fp_library/types/
lazy.rs

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