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::explicit::*,
9//! types::*,
10//! };
11//!
12//! let memo = Lazy::<_, RcLazyConfig>::new(|| 10);
13//! let mapped = 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::explicit::*,
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 = map::<LazyBrand<RcLazyConfig>, _, _, _, _>(|x: &i32| *x, &fa);
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 = map::<LazyBrand<RcLazyConfig>, _, _, _, _>(|x: &i32| g(&f(x)), &fa);
67 /// let sequential = map::<LazyBrand<RcLazyConfig>, _, _, _, _>(
68 /// g,
69 /// &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 `Fn` (not `FnOnce`)?
83 ///
84 /// The `func` parameter uses `Fn` rather than `FnOnce` because multi-element
85 /// containers like `Vec` call the closure once per element. `FnOnce` would
86 /// restrict `RefFunctor` to single-element containers. Closures that move
87 /// out of their captures (`FnOnce` but not `Fn`) cannot be used with
88 /// `ref_map`; these are rare and can be restructured by extracting the
89 /// move into a surrounding scope.
90 #[kind(type Of<'a, A: 'a>: 'a;)]
91 pub trait RefFunctor {
92 /// Maps a function over the values in the functor context, where the function takes a reference.
93 #[document_signature]
94 ///
95 #[document_type_parameters(
96 "The lifetime of the values.",
97 "The type of the value(s) inside the functor.",
98 "The type of the result(s) of applying the function."
99 )]
100 ///
101 #[document_parameters(
102 "The function to apply to the value(s) inside the functor.",
103 "The functor instance containing the value(s)."
104 )]
105 ///
106 #[document_returns(
107 "A new functor instance containing the result(s) of applying the function."
108 )]
109 #[document_examples]
110 ///
111 /// ```
112 /// use fp_library::{
113 /// brands::*,
114 /// classes::*,
115 /// types::*,
116 /// };
117 ///
118 /// let memo = Lazy::<_, RcLazyConfig>::new(|| 10);
119 /// let mapped = LazyBrand::<RcLazyConfig>::ref_map(|x: &i32| *x * 2, &memo);
120 /// assert_eq!(*mapped.evaluate(), 20);
121 /// ```
122 fn ref_map<'a, A: 'a, B: 'a>(
123 func: impl Fn(&A) -> B + 'a,
124 fa: &Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>),
125 ) -> Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, B>);
126 }
127}
128
129pub use inner::*;
130
131#[cfg(test)]
132mod tests {
133 use {
134 crate::{
135 brands::*,
136 functions::explicit,
137 types::*,
138 },
139 quickcheck_macros::quickcheck,
140 };
141
142 /// RefFunctor identity law: map(Clone::clone, lazy) evaluates to the same value as lazy.
143 #[quickcheck]
144 fn prop_ref_functor_identity(x: i32) -> bool {
145 let lazy = RcLazy::pure(x);
146 let mapped = explicit::map::<LazyBrand<RcLazyConfig>, _, _, _, _>(|v: &i32| *v, &lazy);
147 *mapped.evaluate() == *lazy.evaluate()
148 }
149
150 /// RefFunctor composition law: map(|x| g(&f(x)), lazy) == map(g, map(f, lazy)).
151 #[quickcheck]
152 fn prop_ref_functor_composition(x: i32) -> bool {
153 let f = |v: &i32| v.wrapping_mul(2);
154 let g = |v: &i32| v.wrapping_add(1);
155 let lazy1 = RcLazy::pure(x);
156 let lazy2 = RcLazy::pure(x);
157 let composed =
158 explicit::map::<LazyBrand<RcLazyConfig>, _, _, _, _>(|v: &i32| g(&f(v)), &lazy1);
159 let sequential = explicit::map::<LazyBrand<RcLazyConfig>, _, _, _, _>(
160 g,
161 &explicit::map::<LazyBrand<RcLazyConfig>, _, _, _, _>(f, &lazy2),
162 );
163 *composed.evaluate() == *sequential.evaluate()
164 }
165}