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.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}