fp_library/types/
lazy.rs

1//! Implementations for [`Lazy`], the type of lazily-computed, memoized values.
2
3use crate::{
4	brands::LazyBrand,
5	classes::{
6		clonable_fn::{ApplyClonableFn, ClonableFn},
7		defer::Defer,
8		monoid::Monoid,
9		once::{ApplyOnce, Once},
10		semigroup::Semigroup,
11	},
12	hkt::Kind1L1T,
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, ClonableFnBrand: ClonableFn, A>(
21	pub ApplyOnce<OnceBrand, A>,
22	pub ApplyClonableFn<'a, ClonableFnBrand, (), A>,
23);
24
25impl<'a, OnceBrand: Once, ClonableFnBrand: ClonableFn, A> Lazy<'a, OnceBrand, ClonableFnBrand, A> {
26	/// Creates a new `Lazy` value from a thunk.
27	///
28	/// The thunk is wrapped in an `ApplyClonableFn` (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	/// # Parameters
36	///
37	/// * `a`: The thunk that produces the value.
38	///
39	/// # Returns
40	///
41	/// A new `Lazy` value.
42	///
43	/// # Examples
44	///
45	/// ```
46	/// use fp_library::types::lazy::Lazy;
47	/// use fp_library::brands::RcFnBrand;
48	/// use fp_library::brands::OnceCellBrand;
49	/// use fp_library::classes::clonable_fn::ClonableFn;
50	///
51	/// let lazy = Lazy::<OnceCellBrand, RcFnBrand, _>::new(<RcFnBrand as ClonableFn>::new(|_| 42));
52	/// ```
53	pub fn new(a: ApplyClonableFn<'a, ClonableFnBrand, (), A>) -> 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	ApplyOnce<OnceBrand, A>: Clone,
98{
99	fn clone(&self) -> Self {
100		Self(self.0.clone(), self.1.clone())
101	}
102}
103
104impl<OnceBrand: Once + 'static, ClonableFnBrand: ClonableFn + 'static> Kind1L1T
105	for LazyBrand<OnceBrand, ClonableFnBrand>
106{
107	type Output<'a, A: 'a> = Lazy<'a, OnceBrand, ClonableFnBrand, A>;
108}
109
110// Note: Lazy cannot implement Functor, Pointed, or Semimonad because these traits
111// require operations to work for all types A, but Lazy requires A: Clone to be
112// forced (memoized). Adding A: Clone bounds to the traits would restrict all
113// other implementations (e.g. Option<NonClone>), which is undesirable.
114//
115// Consequently, Lazy cannot implement Semiapplicative either, as it extends Functor.
116
117impl<'b, OnceBrand: 'b + Once, CFB: 'b + ClonableFn, A: Semigroup + Clone + 'b> Semigroup
118	for Lazy<'b, OnceBrand, CFB, A>
119where
120	ApplyOnce<OnceBrand, A>: Clone,
121{
122	/// Combines two lazy values using the underlying type's `Semigroup` implementation.
123	///
124	/// The combination is itself lazy: the result is a new thunk that, when forced,
125	/// forces both input values and combines them.
126	///
127	/// # Type Signature
128	///
129	/// `forall a. Semigroup a => (Lazy a, Lazy a) -> Lazy a`
130	///
131	/// # Parameters
132	///
133	/// * `a`: The first lazy value.
134	/// * `b`: The second lazy value.
135	///
136	/// # Returns
137	///
138	/// A new lazy value that combines the results.
139	fn append(
140		a: Self,
141		b: Self,
142	) -> Self {
143		Lazy::new(<CFB as ClonableFn>::new(move |_| {
144			Semigroup::append(Lazy::force(a.clone()), Lazy::force(b.clone()))
145		}))
146	}
147}
148
149impl<'b, OnceBrand: 'b + Once, CFB: 'b + ClonableFn, A: Monoid + Clone + 'b> Monoid
150	for Lazy<'b, OnceBrand, CFB, A>
151where
152	ApplyOnce<OnceBrand, A>: Clone,
153{
154	/// Returns the identity element for the lazy value.
155	///
156	/// The result is a lazy value that evaluates to the underlying type's identity element.
157	///
158	/// # Type Signature
159	///
160	/// `forall a. Monoid a => () -> Lazy a`
161	///
162	/// # Returns
163	///
164	/// A lazy value containing the identity element.
165	fn empty() -> Self {
166		Lazy::new(<CFB as ClonableFn>::new(move |_| Monoid::empty()))
167	}
168}
169
170impl<'a, OnceBrand: Once + 'a, CFB: ClonableFn + 'a, A: Clone + 'a> Defer<'a>
171	for Lazy<'a, OnceBrand, CFB, A>
172{
173	/// Defers the construction of a `Lazy` value.
174	///
175	/// This allows creating a `Lazy` value from a computation that produces a `Lazy` value.
176	/// The outer computation is executed only when the result is forced.
177	///
178	/// # Type Signature
179	///
180	/// `forall a. (() -> Lazy a) -> Lazy a`
181	///
182	/// # Parameters
183	///
184	/// * `f`: A thunk that produces a lazy value.
185	///
186	/// # Returns
187	///
188	/// A new lazy value.
189	///
190	/// # Examples
191	///
192	/// ```
193	/// use fp_library::types::lazy::Lazy;
194	/// use fp_library::brands::RcFnBrand;
195	/// use fp_library::brands::OnceCellBrand;
196	/// use fp_library::classes::clonable_fn::ClonableFn;
197	/// use fp_library::classes::defer::Defer;
198	/// use std::rc::Rc;
199	///
200	/// let lazy = Lazy::<OnceCellBrand, RcFnBrand, _>::defer::<RcFnBrand>(
201	///     <RcFnBrand as ClonableFn>::new(|_| Lazy::new(<RcFnBrand as ClonableFn>::new(|_| 42)))
202	/// );
203	/// assert_eq!(Lazy::force(lazy), 42);
204	/// ```
205	fn defer<ClonableFnBrand>(f: ApplyClonableFn<'a, ClonableFnBrand, (), Self>) -> Self
206	where
207		Self: Sized,
208		ClonableFnBrand: ClonableFn + 'a,
209	{
210		Self::new(<CFB as ClonableFn>::new(move |_| Lazy::force(f(()))))
211	}
212}
213
214#[cfg(test)]
215mod tests {
216	use super::*;
217	use crate::{
218		brands::{OnceCellBrand, RcFnBrand},
219		classes::{clonable_fn::ClonableFn, defer::Defer},
220	};
221	use std::{cell::RefCell, rc::Rc};
222
223	/// Tests that `Lazy::force` memoizes the result.
224	#[test]
225	fn force_memoization() {
226		let counter = Rc::new(RefCell::new(0));
227		let counter_clone = counter.clone();
228
229		let lazy =
230			Lazy::<OnceCellBrand, RcFnBrand, _>::new(<RcFnBrand as ClonableFn>::new(move |_| {
231				*counter_clone.borrow_mut() += 1;
232				42
233			}));
234
235		assert_eq!(*counter.borrow(), 0);
236		assert_eq!(Lazy::force(lazy.clone()), 42);
237		assert_eq!(*counter.borrow(), 1);
238		assert_eq!(Lazy::force(lazy), 42);
239		// Since we clone before forcing, and OnceCell is not shared across clones (it's deep cloned),
240		// the counter increments again.
241		assert_eq!(*counter.borrow(), 2);
242	}
243
244	/// Tests that `Lazy::defer` delays execution until forced.
245	#[test]
246	fn defer_execution_order() {
247		let counter = Rc::new(RefCell::new(0));
248		let counter_clone = counter.clone();
249
250		let lazy = Lazy::<OnceCellBrand, RcFnBrand, _>::defer::<RcFnBrand>(
251			<RcFnBrand as ClonableFn>::new(move |_| {
252				*counter_clone.borrow_mut() += 1;
253				Lazy::new(<RcFnBrand as ClonableFn>::new(|_| 42))
254			}),
255		);
256
257		assert_eq!(*counter.borrow(), 0);
258		assert_eq!(Lazy::force(lazy), 42);
259		assert_eq!(*counter.borrow(), 1);
260	}
261}