Skip to main content

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::*,
9//! 	types::*,
10//! };
11//!
12//! let memo = Lazy::<_, RcLazyConfig>::new(|| 10);
13//! let mapped = ref_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::*,
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 = ref_map::<LazyBrand<RcLazyConfig>, _, _>(|x: &i32| *x, fa.clone());
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 = ref_map::<LazyBrand<RcLazyConfig>, _, _>(|x: &i32| g(&f(x)), fa.clone());
67	/// let sequential = ref_map::<LazyBrand<RcLazyConfig>, _, _>(
68	/// 	g,
69	/// 	ref_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 `FnOnce`?
83	///
84	/// The `func` parameter uses `FnOnce` rather than `Fn` because memoized types like
85	/// `Lazy` create a new `Lazy` value capturing the closure. Since the resulting `Lazy`
86	/// will evaluate the closure at most once, `FnOnce` is sufficient and avoids imposing
87	/// unnecessary `Clone` or multi-call constraints on the caller.
88	#[kind(type Of<'a, A: 'a>: 'a;)]
89	pub trait RefFunctor {
90		/// Maps a function over the values in the functor context, where the function takes a reference.
91		#[document_signature]
92		///
93		#[document_type_parameters(
94			"The lifetime of the values.",
95			"The type of the value(s) inside the functor.",
96			"The type of the result(s) of applying the function."
97		)]
98		///
99		#[document_parameters(
100			"The function to apply to the value(s) inside the functor.",
101			"The functor instance containing the value(s)."
102		)]
103		///
104		#[document_returns(
105			"A new functor instance containing the result(s) of applying the function."
106		)]
107		#[document_examples]
108		///
109		/// ```
110		/// use fp_library::{
111		/// 	brands::*,
112		/// 	classes::*,
113		/// 	types::*,
114		/// };
115		///
116		/// let memo = Lazy::<_, RcLazyConfig>::new(|| 10);
117		/// let mapped = LazyBrand::<RcLazyConfig>::ref_map(|x: &i32| *x * 2, memo);
118		/// assert_eq!(*mapped.evaluate(), 20);
119		/// ```
120		fn ref_map<'a, A: 'a, B: 'a>(
121			func: impl FnOnce(&A) -> B + 'a,
122			fa: Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>),
123		) -> Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, B>);
124	}
125
126	/// Maps a function over the values in the functor context, where the function takes a reference.
127	///
128	/// Free function version that dispatches to [the type class' associated function][`RefFunctor::ref_map`].
129	#[document_signature]
130	///
131	#[document_type_parameters(
132		"The lifetime of the values.",
133		"The brand of the functor.",
134		"The type of the value(s) inside the functor.",
135		"The type of the result(s) of applying the function."
136	)]
137	///
138	#[document_parameters(
139		"The function to apply to the value(s) inside the functor.",
140		"The functor instance containing the value(s)."
141	)]
142	///
143	#[document_returns("A new functor instance containing the result(s) of applying the function.")]
144	#[document_examples]
145	///
146	/// ```
147	/// use fp_library::{
148	/// 	brands::*,
149	/// 	functions::*,
150	/// 	types::*,
151	/// };
152	///
153	/// let memo = Lazy::<_, RcLazyConfig>::new(|| 10);
154	/// let mapped = ref_map::<LazyBrand<RcLazyConfig>, _, _>(|x: &i32| *x * 2, memo);
155	/// assert_eq!(*mapped.evaluate(), 20);
156	/// ```
157	pub fn ref_map<'a, Brand: RefFunctor, A: 'a, B: 'a>(
158		func: impl FnOnce(&A) -> B + 'a,
159		fa: Apply!(<Brand as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>),
160	) -> Apply!(<Brand as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, B>) {
161		Brand::ref_map(func, fa)
162	}
163}
164
165pub use inner::*;
166
167#[cfg(test)]
168mod tests {
169	use {
170		crate::{
171			brands::*,
172			functions::*,
173			types::*,
174		},
175		quickcheck_macros::quickcheck,
176	};
177
178	/// RefFunctor identity law: ref_map(Clone::clone, lazy) evaluates to the same value as lazy.
179	#[quickcheck]
180	fn prop_ref_functor_identity(x: i32) -> bool {
181		let lazy = RcLazy::pure(x);
182		let mapped = ref_map::<LazyBrand<RcLazyConfig>, _, _>(|v: &i32| *v, lazy.clone());
183		*mapped.evaluate() == *lazy.evaluate()
184	}
185
186	/// RefFunctor composition law: ref_map(|x| g(&f(x)), lazy) == ref_map(g, ref_map(f, lazy)).
187	#[quickcheck]
188	fn prop_ref_functor_composition(x: i32) -> bool {
189		let f = |v: &i32| v.wrapping_mul(2);
190		let g = |v: &i32| v.wrapping_add(1);
191		let lazy1 = RcLazy::pure(x);
192		let lazy2 = RcLazy::pure(x);
193		let composed = ref_map::<LazyBrand<RcLazyConfig>, _, _>(|v: &i32| g(&f(v)), lazy1);
194		let sequential = ref_map::<LazyBrand<RcLazyConfig>, _, _>(
195			g,
196			ref_map::<LazyBrand<RcLazyConfig>, _, _>(f, lazy2),
197		);
198		*composed.evaluate() == *sequential.evaluate()
199	}
200}