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