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);
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);
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 `Fn` (not `FnOnce`)?
80 ///
81 /// The `func` parameter uses `Fn` rather than `FnOnce` for consistency
82 /// with [`RefFunctor`](crate::classes::RefFunctor), which requires `Fn`
83 /// to support multi-element containers like `Vec`. Closures that move
84 /// out of their captures (`FnOnce` but not `Fn`) cannot be used with
85 /// `send_ref_map`; these are rare and can be restructured by extracting
86 /// the move into a surrounding scope.
87 #[kind(type Of<'a, A: 'a>: 'a;)]
88 pub trait SendRefFunctor {
89 /// Maps a thread-safe function over the values in the functor context, where the function takes a reference.
90 #[document_signature]
91 ///
92 #[document_type_parameters(
93 "The lifetime of the values.",
94 "The type of the value(s) inside the functor.",
95 "The type of the result(s) of applying the function."
96 )]
97 ///
98 #[document_parameters(
99 "The function to apply to the value(s) inside the functor.",
100 "The functor instance containing the value(s)."
101 )]
102 ///
103 #[document_returns(
104 "A new functor instance containing the result(s) of applying the function."
105 )]
106 #[document_examples]
107 ///
108 /// ```
109 /// use fp_library::{
110 /// brands::*,
111 /// classes::*,
112 /// types::*,
113 /// };
114 ///
115 /// let memo = ArcLazy::new(|| 10);
116 /// let mapped = LazyBrand::<ArcLazyConfig>::send_ref_map(|x: &i32| *x * 2, &memo);
117 /// assert_eq!(*mapped.evaluate(), 20);
118 /// ```
119 fn send_ref_map<'a, A: Send + Sync + 'a, B: Send + Sync + 'a>(
120 func: impl Fn(&A) -> B + Send + 'a,
121 fa: &Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>),
122 ) -> Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, B>);
123 }
124
125 /// Maps a thread-safe function over the values in the functor context, where the function takes a reference.
126 ///
127 /// Free function version that dispatches to [the type class' associated function][`SendRefFunctor::send_ref_map`].
128 #[document_signature]
129 ///
130 #[document_type_parameters(
131 "The lifetime of the values.",
132 "The brand of the functor.",
133 "The type of the value(s) inside the functor.",
134 "The type of the result(s) of applying the function."
135 )]
136 ///
137 #[document_parameters(
138 "The function to apply to the value(s) inside the functor.",
139 "The functor instance containing the value(s)."
140 )]
141 ///
142 #[document_returns("A new functor instance containing the result(s) of applying the function.")]
143 #[document_examples]
144 ///
145 /// ```
146 /// use fp_library::{
147 /// brands::*,
148 /// functions::*,
149 /// types::*,
150 /// };
151 ///
152 /// let memo = ArcLazy::new(|| 10);
153 /// let mapped = send_ref_map::<LazyBrand<ArcLazyConfig>, _, _>(|x: &i32| *x * 2, &memo);
154 /// assert_eq!(*mapped.evaluate(), 20);
155 /// ```
156 pub fn send_ref_map<'a, Brand: SendRefFunctor, A: Send + Sync + 'a, B: Send + Sync + 'a>(
157 func: impl Fn(&A) -> B + Send + 'a,
158 fa: &Apply!(<Brand as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>),
159 ) -> Apply!(<Brand as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, B>) {
160 Brand::send_ref_map(func, fa)
161 }
162}
163
164pub use inner::*;
165
166#[cfg(test)]
167mod tests {
168 use {
169 crate::{
170 brands::*,
171 functions::*,
172 types::*,
173 },
174 quickcheck_macros::quickcheck,
175 };
176
177 /// SendRefFunctor identity law: send_ref_map(Clone::clone, lazy) evaluates to the same value as lazy.
178 #[quickcheck]
179 fn prop_send_ref_functor_identity(x: i32) -> bool {
180 let lazy = ArcLazy::pure(x);
181 let mapped = send_ref_map::<LazyBrand<ArcLazyConfig>, _, _>(|v: &i32| *v, &lazy);
182 *mapped.evaluate() == *lazy.evaluate()
183 }
184
185 /// SendRefFunctor composition law: send_ref_map(|x| g(&f(x)), lazy) == send_ref_map(g, send_ref_map(f, lazy)).
186 #[quickcheck]
187 fn prop_send_ref_functor_composition(x: i32) -> bool {
188 let f = |v: &i32| v.wrapping_mul(2);
189 let g = |v: &i32| v.wrapping_add(1);
190 let lazy1 = ArcLazy::pure(x);
191 let lazy2 = ArcLazy::pure(x);
192 let composed = send_ref_map::<LazyBrand<ArcLazyConfig>, _, _>(|v: &i32| g(&f(v)), &lazy1);
193 let sequential = send_ref_map::<LazyBrand<ArcLazyConfig>, _, _>(
194 g,
195 &send_ref_map::<LazyBrand<ArcLazyConfig>, _, _>(f, &lazy2),
196 );
197 *composed.evaluate() == *sequential.evaluate()
198 }
199}