Skip to main content

fp_library/types/
endofunction.rs

1//! 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 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 crate::{
8		classes::{CloneableFn, Monoid, Semigroup},
9		functions::identity,
10	};
11	use fp_macros::{document_fields, document_parameters, document_type_parameters};
12	use std::{
13		fmt::{self, Debug, Formatter},
14		hash::Hash,
15	};
16
17	/// A wrapper for endofunctions (functions from a set to the same set) that enables monoidal operations.
18	///
19	/// `Endofunction a` represents a function `a -> a`.
20	///
21	/// It exists to provide a monoid instance where:
22	///
23	/// * The binary operation [append][Semigroup::append] is [function composition][crate::functions::compose].
24	/// * The identity element [empty][Monoid::empty] is the [identity function][crate::functions::identity].
25	///
26	/// The wrapped function can be accessed directly via the [`.0` field][Endofunction#structfield.0].
27	///
28	/// ### Type Parameters
29	///
30	#[document_type_parameters(
31		"The lifetime of the function and its captured data.",
32		"The brand of the cloneable function wrapper.",
33		"The input and output type of the function."
34	)]
35	///
36	/// ### Fields
37	///
38	#[document_fields("The wrapped function.")]
39	///
40	/// ### Examples
41	///
42	/// ```
43	/// use fp_library::{brands::*, functions::*, types::*};
44	///
45	/// let f = Endofunction::<RcFnBrand, _>::new(cloneable_fn_new::<RcFnBrand, _, _>(|x: i32| x * 2));
46	/// assert_eq!(f.0(5), 10);
47	/// ```
48	pub struct Endofunction<'a, FnBrand: CloneableFn, A>(
49		pub <FnBrand as CloneableFn>::Of<'a, A, A>,
50	);
51
52	#[document_type_parameters(
53		"The lifetime of the function and its captured data.",
54		"The brand of the function (e.g., `RcFnBrand`).",
55		"The input and output type of the function."
56	)]
57	impl<'a, FnBrand: CloneableFn, A> Endofunction<'a, FnBrand, A> {
58		/// Creates a new `Endofunction`.
59		///
60		/// This function wraps a function `a -> a` in an `Endofunction` struct.
61		///
62		/// ### Type Signature
63		///
64		#[document_signature]
65		///
66		/// ### Parameters
67		///
68		#[document_parameters("The function to wrap.")]
69		///
70		/// ### Returns
71		///
72		/// A new `Endofunction`.
73		///
74		/// ### Examples
75		///
76		/// ```
77		/// use fp_library::{brands::*, functions::*, types::*};
78		///
79		/// let f = Endofunction::<RcFnBrand, _>::new(cloneable_fn_new::<RcFnBrand, _, _>(|x: i32| x * 2));
80		/// assert_eq!(f.0(5), 10);
81		/// ```
82		pub fn new(f: <FnBrand as CloneableFn>::Of<'a, A, A>) -> Self {
83			Self(f)
84		}
85	}
86
87	/// ### Type Parameters
88	///
89	#[document_type_parameters(
90		"The lifetime of the function and its captured data.",
91		"The brand of the function (e.g., `RcFnBrand`).",
92		"The input and output type of the function."
93	)]
94	#[document_parameters("The function to clone.")]
95	impl<'a, FnBrand: CloneableFn, A> Clone for Endofunction<'a, FnBrand, A> {
96		/// ### Type Signature
97		///
98		#[document_signature]
99		fn clone(&self) -> Self {
100			Self::new(self.0.clone())
101		}
102	}
103
104	/// ### Type Parameters
105	///
106	#[document_type_parameters(
107		"The lifetime of the function and its captured data.",
108		"The brand of the function (e.g., `RcFnBrand`).",
109		"The input and output type of the function."
110	)]
111	#[document_parameters("The function to format.")]
112	impl<'a, FnBrand: CloneableFn, A> Debug for Endofunction<'a, FnBrand, A>
113	where
114		<FnBrand as CloneableFn>::Of<'a, A, A>: Debug,
115	{
116		/// ### Type Signature
117		///
118		#[document_signature]
119		///
120		/// ### Parameters
121		///
122		#[document_parameters("The formatter to use.")]
123		fn fmt(
124			&self,
125			fmt: &mut Formatter<'_>,
126		) -> fmt::Result {
127			fmt.debug_tuple("Endofunction").field(&self.0).finish()
128		}
129	}
130
131	/// ### Type Parameters
132	///
133	#[document_type_parameters(
134		"The lifetime of the function and its captured data.",
135		"The brand of the function (e.g., `RcFnBrand`).",
136		"The input and output type of the function."
137	)]
138	impl<'a, FnBrand: CloneableFn, A> Eq for Endofunction<'a, FnBrand, A> where
139		<FnBrand as CloneableFn>::Of<'a, A, A>: Eq
140	{
141	}
142
143	/// ### Type Parameters
144	///
145	#[document_type_parameters(
146		"The lifetime of the function and its captured data.",
147		"The brand of the function (e.g., `RcFnBrand`).",
148		"The input and output type of the function."
149	)]
150	#[document_parameters("The function to hash.")]
151	impl<'a, FnBrand: CloneableFn, A> Hash for Endofunction<'a, FnBrand, A>
152	where
153		<FnBrand as CloneableFn>::Of<'a, A, A>: Hash,
154	{
155		/// ### Type Signature
156		///
157		#[document_signature]
158		///
159		/// ### Type Parameters
160		///
161		#[document_type_parameters("The type of the hasher.")]
162		///
163		/// ### Parameters
164		///
165		#[document_parameters("The hasher state to update.")]
166		fn hash<H: std::hash::Hasher>(
167			&self,
168			state: &mut H,
169		) {
170			self.0.hash(state);
171		}
172	}
173
174	/// ### Type Parameters
175	///
176	#[document_type_parameters(
177		"The lifetime of the function and its captured data.",
178		"The brand of the function (e.g., `RcFnBrand`).",
179		"The input and output type of the function."
180	)]
181	#[document_parameters("The function to compare.")]
182	impl<'a, FnBrand: CloneableFn, A> Ord for Endofunction<'a, FnBrand, A>
183	where
184		<FnBrand as CloneableFn>::Of<'a, A, A>: Ord,
185	{
186		/// ### Type Signature
187		///
188		#[document_signature]
189		///
190		/// ### Parameters
191		///
192		#[document_parameters("The other function to compare to.")]
193		fn cmp(
194			&self,
195			other: &Self,
196		) -> std::cmp::Ordering {
197			self.0.cmp(&other.0)
198		}
199	}
200
201	/// ### Type Parameters
202	///
203	#[document_type_parameters(
204		"The lifetime of the function and its captured data.",
205		"The brand of the function (e.g., `RcFnBrand`).",
206		"The input and output type of the function."
207	)]
208	#[document_parameters("The function to compare.")]
209	impl<'a, FnBrand: CloneableFn, A> PartialEq for Endofunction<'a, FnBrand, A>
210	where
211		<FnBrand as CloneableFn>::Of<'a, A, A>: PartialEq,
212	{
213		/// ### Type Signature
214		///
215		#[document_signature]
216		///
217		/// ### Parameters
218		///
219		#[document_parameters("The other function to compare to.")]
220		fn eq(
221			&self,
222			other: &Self,
223		) -> bool {
224			self.0 == other.0
225		}
226	}
227
228	/// ### Type Parameters
229	///
230	#[document_type_parameters(
231		"The lifetime of the function and its captured data.",
232		"The brand of the function (e.g., `RcFnBrand`).",
233		"The input and output type of the function."
234	)]
235	#[document_parameters("The function to compare.")]
236	impl<'a, FnBrand: CloneableFn, A> PartialOrd for Endofunction<'a, FnBrand, A>
237	where
238		<FnBrand as CloneableFn>::Of<'a, A, A>: PartialOrd,
239	{
240		/// ### Type Signature
241		///
242		#[document_signature]
243		///
244		/// ### Parameters
245		///
246		#[document_parameters("The other function to compare to.")]
247		fn partial_cmp(
248			&self,
249			other: &Self,
250		) -> Option<std::cmp::Ordering> {
251			self.0.partial_cmp(&other.0)
252		}
253	}
254
255	/// ### Type Parameters
256	///
257	#[document_type_parameters(
258		"The lifetime of the function and its captured data.",
259		"The brand of the function (e.g., `RcFnBrand`).",
260		"The input and output type of the function."
261	)]
262	impl<'a, FnBrand: 'a + CloneableFn, A: 'a> Semigroup for Endofunction<'a, FnBrand, A> {
263		/// The result of combining the two values using the semigroup operation.
264		///
265		/// This method composes two endofunctions into a single endofunction.
266		/// Note that `Endofunction` composition is reversed relative to standard function composition:
267		/// `append(f, g)` results in `f . g` (read as "f after g"), meaning `g` is applied first, then `f`.
268		///
269		/// ### Type Signature
270		///
271		#[document_signature]
272		///
273		/// ### Parameters
274		///
275		#[document_parameters(
276			"The second function to apply (the outer function).",
277			"The first function to apply (the inner function)."
278		)]
279		///
280		/// ### Returns
281		///
282		/// The composed function `a . b`.
283		///
284		/// ### Examples
285		///
286		/// ```
287		/// use fp_library::{brands::*, functions::*, types::*};
288		///
289		/// let f = Endofunction::<RcFnBrand, _>::new(cloneable_fn_new::<RcFnBrand, _, _>(|x: i32| x * 2));
290		/// let g = Endofunction::<RcFnBrand, _>::new(cloneable_fn_new::<RcFnBrand, _, _>(|x: i32| x + 1));
291		///
292		/// // f(g(x)) = (x + 1) * 2
293		/// let h = append::<_>(f, g);
294		/// assert_eq!(h.0(5), 12);
295		/// ```
296		fn append(
297			a: Self,
298			b: Self,
299		) -> Self {
300			let f = a.0;
301			let g = b.0;
302			// Compose: f . g
303			Self::new(<FnBrand as CloneableFn>::new(move |x| f(g(x))))
304		}
305	}
306
307	/// ### Type Parameters
308	///
309	#[document_type_parameters(
310		"The lifetime of the function and its captured data.",
311		"The brand of the function (e.g., `RcFnBrand`).",
312		"The input and output type of the function."
313	)]
314	impl<'a, FnBrand: 'a + CloneableFn, A: 'a> Monoid for Endofunction<'a, FnBrand, A> {
315		/// The identity element.
316		///
317		/// This method returns the identity endofunction, which wraps the identity function.
318		///
319		/// ### Type Signature
320		///
321		#[document_signature]
322		///
323		/// ### Returns
324		///
325		/// The identity endofunction.
326		///
327		/// ### Examples
328		///
329		/// ```
330		/// use fp_library::{brands::*, functions::*, types::*};
331		///
332		/// let id = empty::<Endofunction<RcFnBrand, i32>>();
333		/// assert_eq!(id.0(5), 5);
334		/// ```
335		fn empty() -> Self {
336			Self::new(<FnBrand as CloneableFn>::new(identity))
337		}
338	}
339}
340pub use inner::*;
341
342#[cfg(test)]
343mod tests {
344	use super::*;
345	use crate::{
346		brands::RcFnBrand,
347		classes::{cloneable_fn::CloneableFn, monoid::empty, semigroup::append},
348	};
349	use quickcheck_macros::quickcheck;
350
351	// Semigroup Laws
352
353	/// Tests the associativity law for Semigroup.
354	#[quickcheck]
355	fn semigroup_associativity(val: i32) -> bool {
356		let f = Endofunction::<RcFnBrand, _>::new(<RcFnBrand as CloneableFn>::new(|x: i32| {
357			x.wrapping_add(1)
358		}));
359		let g = Endofunction::<RcFnBrand, _>::new(<RcFnBrand as CloneableFn>::new(|x: i32| {
360			x.wrapping_mul(2)
361		}));
362		let h = Endofunction::<RcFnBrand, _>::new(<RcFnBrand as CloneableFn>::new(|x: i32| {
363			x.wrapping_sub(3)
364		}));
365
366		let lhs = append(f.clone(), append(g.clone(), h.clone()));
367		let rhs = append(append(f, g), h);
368
369		lhs.0(val) == rhs.0(val)
370	}
371
372	// Monoid Laws
373
374	/// Tests the left identity law for Monoid.
375	#[quickcheck]
376	fn monoid_left_identity(val: i32) -> bool {
377		let f = Endofunction::<RcFnBrand, _>::new(<RcFnBrand as CloneableFn>::new(|x: i32| {
378			x.wrapping_add(1)
379		}));
380		let id = empty::<Endofunction<RcFnBrand, i32>>();
381
382		let res = append(id, f.clone());
383		res.0(val) == f.0(val)
384	}
385
386	/// Tests the right identity law for Monoid.
387	#[quickcheck]
388	fn monoid_right_identity(val: i32) -> bool {
389		let f = Endofunction::<RcFnBrand, _>::new(<RcFnBrand as CloneableFn>::new(|x: i32| {
390			x.wrapping_add(1)
391		}));
392		let id = empty::<Endofunction<RcFnBrand, i32>>();
393
394		let res = append(f.clone(), id);
395		res.0(val) == f.0(val)
396	}
397}