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