Skip to main content

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}