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}