Skip to main content

fp_library/types/
send_endofunction.rs

1//! Thread-safe wrapper for endofunctions (functions `a -> a`) with [`Semigroup`](crate::classes::Semigroup) and [`Monoid`](crate::classes::Monoid) instances based on function composition.
2//!
3//! Used to treat function composition as a monoidal operation in thread-safe contexts where [`append`](crate::functions::append) composes functions and [`empty`](crate::functions::empty) is the identity function.
4
5#[fp_macros::document_module]
6mod inner {
7	use {
8		crate::{
9			classes::{
10				send_clone_fn::SendLiftFn,
11				*,
12			},
13			functions::identity,
14		},
15		fp_macros::*,
16		std::fmt::{
17			self,
18			Debug,
19			Formatter,
20		},
21	};
22
23	/// A thread-safe wrapper for endofunctions that enables monoidal operations.
24	///
25	/// `SendEndofunction a` represents a function `a -> a` wrapped in an `Arc<dyn Fn>`.
26	///
27	/// It exists to provide a monoid instance where:
28	///
29	/// * The binary operation [append][Semigroup::append] is function composition.
30	/// * The identity element [empty][Monoid::empty] is the identity function.
31	///
32	/// This is the `Send + Sync` counterpart of [`Endofunction`](crate::types::Endofunction).
33	#[document_type_parameters(
34		"The lifetime of the function and its captured data.",
35		"The brand of the thread-safe cloneable function wrapper.",
36		"The input and output type of the function."
37	)]
38	///
39	pub struct SendEndofunction<'a, FnBrand: SendLiftFn, A: 'a>(
40		/// The wrapped function.
41		pub <FnBrand as SendCloneFn>::Of<'a, A, A>,
42	);
43
44	#[document_type_parameters(
45		"The lifetime of the function and its captured data.",
46		"The brand of the function (e.g., `ArcFnBrand`).",
47		"The input and output type of the function."
48	)]
49	impl<'a, FnBrand: SendLiftFn, A: 'a> SendEndofunction<'a, FnBrand, A> {
50		/// Creates a new `SendEndofunction`.
51		#[document_signature]
52		///
53		#[document_parameters("The function to wrap.")]
54		///
55		#[document_returns("A new `SendEndofunction`.")]
56		///
57		#[document_examples]
58		///
59		/// ```
60		/// use fp_library::{
61		/// 	brands::*,
62		/// 	functions::*,
63		/// 	types::*,
64		/// };
65		///
66		/// let f =
67		/// 	SendEndofunction::<ArcFnBrand, _>::new(send_lift_fn_new::<ArcFnBrand, _, _>(|x: i32| {
68		/// 		x * 2
69		/// 	}));
70		/// assert_eq!(f.0(5), 10);
71		/// ```
72		pub fn new(f: <FnBrand as SendCloneFn>::Of<'a, A, A>) -> Self {
73			Self(f)
74		}
75	}
76
77	#[document_type_parameters(
78		"The lifetime of the function and its captured data.",
79		"The brand of the function (e.g., `ArcFnBrand`).",
80		"The input and output type of the function."
81	)]
82	#[document_parameters("The function to clone.")]
83	impl<'a, FnBrand: SendLiftFn, A: 'a> Clone for SendEndofunction<'a, FnBrand, A> {
84		#[document_signature]
85		#[document_returns("The cloned endofunction.")]
86		#[document_examples]
87		///
88		/// ```
89		/// use fp_library::{
90		/// 	brands::*,
91		/// 	functions::*,
92		/// 	types::*,
93		/// };
94		/// let f =
95		/// 	SendEndofunction::<ArcFnBrand, _>::new(send_lift_fn_new::<ArcFnBrand, _, _>(|x: i32| {
96		/// 		x * 2
97		/// 	}));
98		/// let cloned = f.clone();
99		/// assert_eq!(cloned.0(5), 10);
100		/// ```
101		fn clone(&self) -> Self {
102			Self::new(self.0.clone())
103		}
104	}
105
106	#[document_type_parameters(
107		"The lifetime of the function and its captured data.",
108		"The brand of the function (e.g., `ArcFnBrand`).",
109		"The input and output type of the function."
110	)]
111	#[document_parameters("The function to format.")]
112	impl<'a, FnBrand: SendLiftFn, A: 'a> Debug for SendEndofunction<'a, FnBrand, A>
113	where
114		<FnBrand as SendCloneFn>::Of<'a, A, A>: Debug,
115	{
116		#[document_signature]
117		#[document_parameters("The formatter to use.")]
118		#[document_returns("The result of the formatting operation.")]
119		#[document_examples]
120		///
121		/// ```
122		/// use fp_library::{
123		/// 	brands::*,
124		/// 	functions::*,
125		/// 	types::*,
126		/// };
127		/// let f =
128		/// 	SendEndofunction::<ArcFnBrand, _>::new(send_lift_fn_new::<ArcFnBrand, _, _>(|x: i32| {
129		/// 		x * 2
130		/// 	}));
131		/// assert_eq!(f.0(5), 10);
132		/// ```
133		fn fmt(
134			&self,
135			fmt: &mut Formatter<'_>,
136		) -> fmt::Result {
137			fmt.debug_tuple("SendEndofunction").field(&self.0).finish()
138		}
139	}
140
141	#[document_type_parameters(
142		"The lifetime of the function and its captured data.",
143		"The brand of the function (e.g., `ArcFnBrand`).",
144		"The input and output type of the function."
145	)]
146	impl<'a, FnBrand: 'a + SendLiftFn, A: 'a + Send + Sync> Semigroup
147		for SendEndofunction<'a, FnBrand, A>
148	{
149		/// Composes two endofunctions.
150		///
151		/// `append(f, g)` results in `f . g` (read as "f after g"), meaning `g` is applied first, then `f`.
152		#[document_signature]
153		///
154		#[document_parameters(
155			"The second function to apply (the outer function).",
156			"The first function to apply (the inner function)."
157		)]
158		///
159		#[document_returns("The composed function `a . b`.")]
160		#[document_examples]
161		///
162		/// ```
163		/// use fp_library::{
164		/// 	brands::*,
165		/// 	functions::*,
166		/// 	types::*,
167		/// };
168		///
169		/// let f =
170		/// 	SendEndofunction::<ArcFnBrand, _>::new(send_lift_fn_new::<ArcFnBrand, _, _>(|x: i32| {
171		/// 		x * 2
172		/// 	}));
173		/// let g =
174		/// 	SendEndofunction::<ArcFnBrand, _>::new(send_lift_fn_new::<ArcFnBrand, _, _>(|x: i32| {
175		/// 		x + 1
176		/// 	}));
177		///
178		/// // f(g(x)) = (x + 1) * 2
179		/// let h = append::<_>(f, g);
180		/// assert_eq!(h.0(5), 12);
181		/// ```
182		fn append(
183			a: Self,
184			b: Self,
185		) -> Self {
186			let f = a.0;
187			let g = b.0;
188			Self::new(<FnBrand as SendLiftFn>::new(move |x| f(g(x))))
189		}
190	}
191
192	#[document_type_parameters(
193		"The lifetime of the function and its captured data.",
194		"The brand of the function (e.g., `ArcFnBrand`).",
195		"The input and output type of the function."
196	)]
197	impl<'a, FnBrand: 'a + SendLiftFn, A: 'a + Send + Sync> Monoid
198		for SendEndofunction<'a, FnBrand, A>
199	{
200		/// Returns the identity endofunction.
201		#[document_signature]
202		///
203		#[document_returns("The identity endofunction.")]
204		///
205		#[document_examples]
206		///
207		/// ```
208		/// use fp_library::{
209		/// 	brands::*,
210		/// 	functions::*,
211		/// 	types::*,
212		/// };
213		///
214		/// let id = empty::<SendEndofunction<ArcFnBrand, i32>>();
215		/// assert_eq!(id.0(5), 5);
216		/// ```
217		fn empty() -> Self {
218			Self::new(<FnBrand as SendLiftFn>::new(identity))
219		}
220	}
221}
222pub use inner::*;
223
224#[cfg(test)]
225mod tests {
226	use {
227		super::*,
228		crate::{
229			brands::ArcFnBrand,
230			classes::send_clone_fn::SendLiftFn,
231			functions::*,
232		},
233		quickcheck_macros::quickcheck,
234	};
235
236	#[quickcheck]
237	fn semigroup_associativity(val: i32) -> bool {
238		let f =
239			SendEndofunction::<ArcFnBrand, _>::new(<ArcFnBrand as SendLiftFn>::new(|x: i32| {
240				x.wrapping_add(1)
241			}));
242		let g =
243			SendEndofunction::<ArcFnBrand, _>::new(<ArcFnBrand as SendLiftFn>::new(|x: i32| {
244				x.wrapping_mul(2)
245			}));
246		let h =
247			SendEndofunction::<ArcFnBrand, _>::new(<ArcFnBrand as SendLiftFn>::new(|x: i32| {
248				x.wrapping_sub(3)
249			}));
250
251		let lhs = append(f.clone(), append(g.clone(), h.clone()));
252		let rhs = append(append(f, g), h);
253
254		lhs.0(val) == rhs.0(val)
255	}
256
257	#[quickcheck]
258	fn monoid_left_identity(val: i32) -> bool {
259		let f =
260			SendEndofunction::<ArcFnBrand, _>::new(<ArcFnBrand as SendLiftFn>::new(|x: i32| {
261				x.wrapping_add(1)
262			}));
263		let id = empty::<SendEndofunction<ArcFnBrand, i32>>();
264
265		let res = append(id, f.clone());
266		res.0(val) == f.0(val)
267	}
268
269	#[quickcheck]
270	fn monoid_right_identity(val: i32) -> bool {
271		let f =
272			SendEndofunction::<ArcFnBrand, _>::new(<ArcFnBrand as SendLiftFn>::new(|x: i32| {
273				x.wrapping_add(1)
274			}));
275		let id = empty::<SendEndofunction<ArcFnBrand, i32>>();
276
277		let res = append(f.clone(), id);
278		res.0(val) == f.0(val)
279	}
280}