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::explicit::*,
9//! 	types::*,
10//! };
11//!
12//! let memo = Lazy::<_, RcLazyConfig>::new(|| 10);
13//! let mapped = 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::explicit::*,
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 = map::<LazyBrand<RcLazyConfig>, _, _, _, _>(|x: &i32| *x, &fa);
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 = map::<LazyBrand<RcLazyConfig>, _, _, _, _>(|x: &i32| g(&f(x)), &fa);
67	/// let sequential = map::<LazyBrand<RcLazyConfig>, _, _, _, _>(
68	/// 	g,
69	/// 	&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 `Fn` (not `FnOnce`)?
83	///
84	/// The `func` parameter uses `Fn` rather than `FnOnce` because multi-element
85	/// containers like `Vec` call the closure once per element. `FnOnce` would
86	/// restrict `RefFunctor` to single-element containers. Closures that move
87	/// out of their captures (`FnOnce` but not `Fn`) cannot be used with
88	/// `ref_map`; these are rare and can be restructured by extracting the
89	/// move into a surrounding scope.
90	#[kind(type Of<'a, A: 'a>: 'a;)]
91	pub trait RefFunctor {
92		/// Maps a function over the values in the functor context, where the function takes a reference.
93		#[document_signature]
94		///
95		#[document_type_parameters(
96			"The lifetime of the values.",
97			"The type of the value(s) inside the functor.",
98			"The type of the result(s) of applying the function."
99		)]
100		///
101		#[document_parameters(
102			"The function to apply to the value(s) inside the functor.",
103			"The functor instance containing the value(s)."
104		)]
105		///
106		#[document_returns(
107			"A new functor instance containing the result(s) of applying the function."
108		)]
109		#[document_examples]
110		///
111		/// ```
112		/// use fp_library::{
113		/// 	brands::*,
114		/// 	classes::*,
115		/// 	types::*,
116		/// };
117		///
118		/// let memo = Lazy::<_, RcLazyConfig>::new(|| 10);
119		/// let mapped = LazyBrand::<RcLazyConfig>::ref_map(|x: &i32| *x * 2, &memo);
120		/// assert_eq!(*mapped.evaluate(), 20);
121		/// ```
122		fn ref_map<'a, A: 'a, B: 'a>(
123			func: impl Fn(&A) -> B + 'a,
124			fa: &Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, A>),
125		) -> Apply!(<Self as Kind!( type Of<'a, T: 'a>: 'a; )>::Of<'a, B>);
126	}
127}
128
129pub use inner::*;
130
131#[cfg(test)]
132mod tests {
133	use {
134		crate::{
135			brands::*,
136			functions::explicit,
137			types::*,
138		},
139		quickcheck_macros::quickcheck,
140	};
141
142	/// RefFunctor identity law: map(Clone::clone, lazy) evaluates to the same value as lazy.
143	#[quickcheck]
144	fn prop_ref_functor_identity(x: i32) -> bool {
145		let lazy = RcLazy::pure(x);
146		let mapped = explicit::map::<LazyBrand<RcLazyConfig>, _, _, _, _>(|v: &i32| *v, &lazy);
147		*mapped.evaluate() == *lazy.evaluate()
148	}
149
150	/// RefFunctor composition law: map(|x| g(&f(x)), lazy) == map(g, map(f, lazy)).
151	#[quickcheck]
152	fn prop_ref_functor_composition(x: i32) -> bool {
153		let f = |v: &i32| v.wrapping_mul(2);
154		let g = |v: &i32| v.wrapping_add(1);
155		let lazy1 = RcLazy::pure(x);
156		let lazy2 = RcLazy::pure(x);
157		let composed =
158			explicit::map::<LazyBrand<RcLazyConfig>, _, _, _, _>(|v: &i32| g(&f(v)), &lazy1);
159		let sequential = explicit::map::<LazyBrand<RcLazyConfig>, _, _, _, _>(
160			g,
161			&explicit::map::<LazyBrand<RcLazyConfig>, _, _, _, _>(f, &lazy2),
162		);
163		*composed.evaluate() == *sequential.evaluate()
164	}
165}