fp_library/classes/ref_functor.rs
1//! Types that can be mapped over by receiving or returning references to their contents.
2//!
3//! ### Examples
4//!
5//! ```
6//! use fp_library::{
7//! brands::*,
8//! functions::*,
9//! types::*,
10//! };
11//!
12//! let memo = Lazy::<_, RcLazyConfig>::new(|| 10);
13//! let mapped = ref_map::<LazyBrand<RcLazyConfig>, _, _>(|x: &i32| *x * 2, memo);
14//! assert_eq!(*mapped.evaluate(), 20);
15//! ```
16
17#[fp_macros::document_module]
18mod inner {
19 use {
20 crate::kinds::*,
21 fp_macros::*,
22 };
23
24 /// A type class for types that can be mapped over, returning references.
25 ///
26 /// This is a variant of `Functor` for types where `map` receives/returns references.
27 /// This is required for types like `Lazy` where `get()` returns `&A`, not `A`.
28 ///
29 /// `RefFunctor` is intentionally independent from
30 /// [`SendRefFunctor`](crate::classes::SendRefFunctor). Although one might
31 /// expect `SendRefFunctor` to be a subtrait of `RefFunctor`, this is not the case because
32 /// `ArcLazy::new` requires `Send` on the closure, which a generic `RefFunctor` cannot
33 /// guarantee. As a result, `ArcLazy` implements only `SendRefFunctor`, not `RefFunctor`,
34 /// and `RcLazy` implements only `RefFunctor`, not `SendRefFunctor`.
35 ///
36 /// ### Laws
37 ///
38 /// `RefFunctor` instances must satisfy the following laws:
39 ///
40 /// **Identity:** `ref_map(|x| x.clone(), fa)` is equivalent to `fa`, given `A: Clone`.
41 /// The `Clone` requirement arises because the mapping function receives `&A` but must
42 /// produce a value of type `A` to satisfy the identity law.
43 ///
44 /// **Composition:** `ref_map(|x| g(&f(x)), fa)` is equivalent to
45 /// `ref_map(g, ref_map(f, fa))`.
46 #[document_examples]
47 ///
48 /// RefFunctor laws for [`Lazy`](crate::types::Lazy):
49 ///
50 /// ```
51 /// use fp_library::{
52 /// brands::*,
53 /// functions::*,
54 /// types::*,
55 /// };
56 ///
57 /// // Identity: ref_map(|x| x.clone(), fa) evaluates to the same value as fa.
58 /// let fa = RcLazy::pure(5);
59 /// let mapped = ref_map::<LazyBrand<RcLazyConfig>, _, _>(|x: &i32| *x, fa.clone());
60 /// assert_eq!(*mapped.evaluate(), *fa.evaluate());
61 ///
62 /// // Composition: ref_map(|x| g(&f(x)), fa) = ref_map(g, ref_map(f, fa))
63 /// let f = |x: &i32| *x * 2;
64 /// let g = |x: &i32| x + 1;
65 /// let fa = RcLazy::pure(5);
66 /// let composed = ref_map::<LazyBrand<RcLazyConfig>, _, _>(|x: &i32| g(&f(x)), fa.clone());
67 /// let sequential = ref_map::<LazyBrand<RcLazyConfig>, _, _>(
68 /// g,
69 /// ref_map::<LazyBrand<RcLazyConfig>, _, _>(f, fa),
70 /// );
71 /// assert_eq!(*composed.evaluate(), *sequential.evaluate());
72 /// ```
73 ///
74 /// # Cache chain behavior
75 ///
76 /// Chaining `ref_map` calls on memoized types like [`Lazy`](crate::types::Lazy) creates
77 /// a linked list of `Rc`/`Arc`-referenced cells. Each mapped value retains a reference to
78 /// its predecessor, so the entire chain of predecessor cells stays alive as long as any
79 /// downstream mapped value is reachable. Be aware that long chains can accumulate memory
80 /// that is only freed when the final value in the chain is dropped.
81 ///
82 /// # Why `FnOnce`?
83 ///
84 /// The `func` parameter uses `FnOnce` rather than `Fn` because memoized types like
85 /// `Lazy` create a new `Lazy` value capturing the closure. Since the resulting `Lazy`
86 /// will evaluate the closure at most once, `FnOnce` is sufficient and avoids imposing
87 /// unnecessary `Clone` or multi-call constraints on the caller.
88 #[kind(type Of<'a, A: 'a>: 'a;)]
89 pub trait RefFunctor {
90 /// Maps a function over the values in the functor context, where the function takes a reference.
91 #[document_signature]
92 ///
93 #[document_type_parameters(
94 "The lifetime of the values.",
95 "The type of the value(s) inside the functor.",
96 "The type of the result(s) of applying the function."
97 )]
98 ///
99 #[document_parameters(
100 "The function to apply to the value(s) inside the functor.",
101 "The functor instance containing the value(s)."
102 )]
103 ///
104 #[document_returns(
105 "A new functor instance containing the result(s) of applying the function."
106 )]
107 #[document_examples]
108 ///
109 /// ```
110 /// use fp_library::{
111 /// brands::*,
112 /// classes::*,
113 /// types::*,
114 /// };
115 ///
116 /// let memo = Lazy::<_, RcLazyConfig>::new(|| 10);
117 /// let mapped = LazyBrand::<RcLazyConfig>::ref_map(|x: &i32| *x * 2, memo);
118 /// assert_eq!(*mapped.evaluate(), 20);
119 /// ```
120 fn ref_map<'a, A: 'a, B: 'a>(
121 func: impl FnOnce(&A) -> B + 'a,
122 fa: Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>),
123 ) -> Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, B>);
124 }
125
126 /// Maps a function over the values in the functor context, where the function takes a reference.
127 ///
128 /// Free function version that dispatches to [the type class' associated function][`RefFunctor::ref_map`].
129 #[document_signature]
130 ///
131 #[document_type_parameters(
132 "The lifetime of the values.",
133 "The brand of the functor.",
134 "The type of the value(s) inside the functor.",
135 "The type of the result(s) of applying the function."
136 )]
137 ///
138 #[document_parameters(
139 "The function to apply to the value(s) inside the functor.",
140 "The functor instance containing the value(s)."
141 )]
142 ///
143 #[document_returns("A new functor instance containing the result(s) of applying the function.")]
144 #[document_examples]
145 ///
146 /// ```
147 /// use fp_library::{
148 /// brands::*,
149 /// functions::*,
150 /// types::*,
151 /// };
152 ///
153 /// let memo = Lazy::<_, RcLazyConfig>::new(|| 10);
154 /// let mapped = ref_map::<LazyBrand<RcLazyConfig>, _, _>(|x: &i32| *x * 2, memo);
155 /// assert_eq!(*mapped.evaluate(), 20);
156 /// ```
157 pub fn ref_map<'a, Brand: RefFunctor, A: 'a, B: 'a>(
158 func: impl FnOnce(&A) -> B + 'a,
159 fa: Apply!(<Brand as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>),
160 ) -> Apply!(<Brand as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, B>) {
161 Brand::ref_map(func, fa)
162 }
163}
164
165pub use inner::*;
166
167#[cfg(test)]
168mod tests {
169 use {
170 crate::{
171 brands::*,
172 functions::*,
173 types::*,
174 },
175 quickcheck_macros::quickcheck,
176 };
177
178 /// RefFunctor identity law: ref_map(Clone::clone, lazy) evaluates to the same value as lazy.
179 #[quickcheck]
180 fn prop_ref_functor_identity(x: i32) -> bool {
181 let lazy = RcLazy::pure(x);
182 let mapped = ref_map::<LazyBrand<RcLazyConfig>, _, _>(|v: &i32| *v, lazy.clone());
183 *mapped.evaluate() == *lazy.evaluate()
184 }
185
186 /// RefFunctor composition law: ref_map(|x| g(&f(x)), lazy) == ref_map(g, ref_map(f, lazy)).
187 #[quickcheck]
188 fn prop_ref_functor_composition(x: i32) -> bool {
189 let f = |v: &i32| v.wrapping_mul(2);
190 let g = |v: &i32| v.wrapping_add(1);
191 let lazy1 = RcLazy::pure(x);
192 let lazy2 = RcLazy::pure(x);
193 let composed = ref_map::<LazyBrand<RcLazyConfig>, _, _>(|v: &i32| g(&f(v)), lazy1);
194 let sequential = ref_map::<LazyBrand<RcLazyConfig>, _, _>(
195 g,
196 ref_map::<LazyBrand<RcLazyConfig>, _, _>(f, lazy2),
197 );
198 *composed.evaluate() == *sequential.evaluate()
199 }
200}