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