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}