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}