fp_library/types/
endofunction.rs

1//! Endofunction wrapper.
2//!
3//! This module defines the [`Endofunction`] struct, which wraps a function from a type to itself (an endofunction)
4//! and provides [`Semigroup`] and [`Monoid`] instances based on function composition and identity.
5
6use crate::{
7	classes::{clonable_fn::ClonableFn, monoid::Monoid, semigroup::Semigroup},
8	functions::identity,
9};
10use std::{
11	fmt::{self, Debug, Formatter},
12	hash::Hash,
13};
14
15/// A wrapper for endofunctions (functions from a set to the same set) that enables monoidal operations.
16///
17/// `Endofunction a` represents a function `a -> a`.
18///
19/// It exists to provide a monoid instance where:
20///
21/// * The binary operation [append][Semigroup::append] is [function composition][crate::functions::compose].
22/// * The identity element [empty][Monoid::empty] is the [identity function][crate::functions::identity].
23///
24/// The wrapped function can be accessed directly via the [`.0` field][Endofunction#structfield.0].
25pub struct Endofunction<'a, FnBrand: ClonableFn, A>(pub <FnBrand as ClonableFn>::Of<'a, A, A>);
26
27impl<'a, FnBrand: ClonableFn, A> Endofunction<'a, FnBrand, A> {
28	/// Creates a new `Endofunction`.
29	///
30	/// This function wraps a function `a -> a` in an `Endofunction` struct.
31	///
32	/// ### Type Signature
33	///
34	/// `forall a. (a -> a) -> Endofunction a`
35	///
36	/// ### Type Parameters
37	///
38	/// * `FnBrand`: The brand of the function (e.g., `RcFnBrand`).
39	/// * `A`: The input and output type of the function.
40	///
41	/// ### Parameters
42	///
43	/// * `f`: The function to wrap.
44	///
45	/// ### Returns
46	///
47	/// A new `Endofunction`.
48	///
49	/// ### Examples
50	///
51	/// ```
52	/// use fp_library::types::endofunction::Endofunction;
53	/// use fp_library::brands::RcFnBrand;
54	/// use fp_library::classes::clonable_fn::ClonableFn;
55	///
56	/// let f = Endofunction::<RcFnBrand, _>::new(<RcFnBrand as ClonableFn>::new(|x: i32| x * 2));
57	/// assert_eq!(f.0(5), 10);
58	/// ```
59	pub fn new(f: <FnBrand as ClonableFn>::Of<'a, A, A>) -> Self {
60		Self(f)
61	}
62}
63
64impl<'a, FnBrand: ClonableFn, A> Clone for Endofunction<'a, FnBrand, A> {
65	fn clone(&self) -> Self {
66		Self::new(self.0.clone())
67	}
68}
69
70impl<'a, FnBrand: ClonableFn, A> Debug for Endofunction<'a, FnBrand, A>
71where
72	<FnBrand as ClonableFn>::Of<'a, A, A>: Debug,
73{
74	fn fmt(
75		&self,
76		fmt: &mut Formatter<'_>,
77	) -> fmt::Result {
78		fmt.debug_tuple("Endofunction").field(&self.0).finish()
79	}
80}
81
82impl<'a, FnBrand: ClonableFn, A> Eq for Endofunction<'a, FnBrand, A> where
83	<FnBrand as ClonableFn>::Of<'a, A, A>: Eq
84{
85}
86
87impl<'a, FnBrand: ClonableFn, A> Hash for Endofunction<'a, FnBrand, A>
88where
89	<FnBrand as ClonableFn>::Of<'a, A, A>: Hash,
90{
91	fn hash<H: std::hash::Hasher>(
92		&self,
93		state: &mut H,
94	) {
95		self.0.hash(state);
96	}
97}
98
99impl<'a, FnBrand: ClonableFn, A> Ord for Endofunction<'a, FnBrand, A>
100where
101	<FnBrand as ClonableFn>::Of<'a, A, A>: Ord,
102{
103	fn cmp(
104		&self,
105		other: &Self,
106	) -> std::cmp::Ordering {
107		self.0.cmp(&other.0)
108	}
109}
110
111impl<'a, FnBrand: ClonableFn, A> PartialEq for Endofunction<'a, FnBrand, A>
112where
113	<FnBrand as ClonableFn>::Of<'a, A, A>: PartialEq,
114{
115	fn eq(
116		&self,
117		other: &Self,
118	) -> bool {
119		self.0 == other.0
120	}
121}
122
123impl<'a, FnBrand: ClonableFn, A> PartialOrd for Endofunction<'a, FnBrand, A>
124where
125	<FnBrand as ClonableFn>::Of<'a, A, A>: PartialOrd,
126{
127	fn partial_cmp(
128		&self,
129		other: &Self,
130	) -> Option<std::cmp::Ordering> {
131		self.0.partial_cmp(&other.0)
132	}
133}
134
135impl<'a, FnBrand: 'a + ClonableFn, A: 'a> Semigroup for Endofunction<'a, FnBrand, A> {
136	/// The result of combining the two values using the semigroup operation.
137	///
138	/// This method composes two endofunctions into a single endofunction.
139	/// Note that `Endofunction` composition is reversed relative to standard function composition:
140	/// `append(f, g)` results in `f . g` (read as "f after g"), meaning `g` is applied first, then `f`.
141	///
142	/// ### Type Signature
143	///
144	/// `forall a. Semigroup (Endofunction a) => (Endofunction a, Endofunction a) -> Endofunction a`
145	///
146	/// ### Parameters
147	///
148	/// * `a`: The second function to apply (the outer function).
149	/// * `b`: The first function to apply (the inner function).
150	///
151	/// ### Returns
152	///
153	/// The composed function `a . b`.
154	///
155	/// ### Examples
156	///
157	/// ```
158	/// use fp_library::types::endofunction::Endofunction;
159	/// use fp_library::brands::RcFnBrand;
160	/// use fp_library::classes::clonable_fn::ClonableFn;
161	/// use fp_library::classes::semigroup::Semigroup;
162	///
163	/// let f = Endofunction::<RcFnBrand, _>::new(<RcFnBrand as ClonableFn>::new(|x: i32| x * 2));
164	/// let g = Endofunction::<RcFnBrand, _>::new(<RcFnBrand as ClonableFn>::new(|x: i32| x + 1));
165	///
166	/// // f(g(x)) = (x + 1) * 2
167	/// let h = Semigroup::append(f, g);
168	/// assert_eq!(h.0(5), 12);
169	/// ```
170	fn append(
171		a: Self,
172		b: Self,
173	) -> Self {
174		let f = a.0;
175		let g = b.0;
176		// Compose: f . g
177		Self::new(<FnBrand as ClonableFn>::new(move |x| f(g(x))))
178	}
179}
180
181impl<'a, FnBrand: 'a + ClonableFn, A: 'a> Monoid for Endofunction<'a, FnBrand, A> {
182	/// The identity element.
183	///
184	/// This method returns the identity endofunction, which wraps the identity function.
185	///
186	/// ### Type Signature
187	///
188	/// `forall a. Monoid (Endofunction a) => () -> Endofunction a`
189	///
190	/// ### Returns
191	///
192	/// The identity endofunction.
193	///
194	/// ### Examples
195	///
196	/// ```
197	/// use fp_library::types::endofunction::Endofunction;
198	/// use fp_library::brands::RcFnBrand;
199	/// use fp_library::classes::monoid::Monoid;
200	///
201	/// let id = Endofunction::<RcFnBrand, i32>::empty();
202	/// assert_eq!(id.0(5), 5);
203	/// ```
204	fn empty() -> Self {
205		Self::new(<FnBrand as ClonableFn>::new(identity))
206	}
207}
208
209#[cfg(test)]
210mod tests {
211	use super::*;
212	use crate::{
213		brands::RcFnBrand,
214		classes::{clonable_fn::ClonableFn, monoid::empty, semigroup::append},
215	};
216	use quickcheck_macros::quickcheck;
217
218	// Semigroup Laws
219
220	/// Tests the associativity law for Semigroup.
221	#[quickcheck]
222	fn semigroup_associativity(val: i32) -> bool {
223		let f = Endofunction::<RcFnBrand, _>::new(<RcFnBrand as ClonableFn>::new(|x: i32| {
224			x.wrapping_add(1)
225		}));
226		let g = Endofunction::<RcFnBrand, _>::new(<RcFnBrand as ClonableFn>::new(|x: i32| {
227			x.wrapping_mul(2)
228		}));
229		let h = Endofunction::<RcFnBrand, _>::new(<RcFnBrand as ClonableFn>::new(|x: i32| {
230			x.wrapping_sub(3)
231		}));
232
233		let lhs = append(f.clone(), append(g.clone(), h.clone()));
234		let rhs = append(append(f, g), h);
235
236		lhs.0(val) == rhs.0(val)
237	}
238
239	// Monoid Laws
240
241	/// Tests the left identity law for Monoid.
242	#[quickcheck]
243	fn monoid_left_identity(val: i32) -> bool {
244		let f = Endofunction::<RcFnBrand, _>::new(<RcFnBrand as ClonableFn>::new(|x: i32| {
245			x.wrapping_add(1)
246		}));
247		let id = empty::<Endofunction<RcFnBrand, i32>>();
248
249		let res = append(id, f.clone());
250		res.0(val) == f.0(val)
251	}
252
253	/// Tests the right identity law for Monoid.
254	#[quickcheck]
255	fn monoid_right_identity(val: i32) -> bool {
256		let f = Endofunction::<RcFnBrand, _>::new(<RcFnBrand as ClonableFn>::new(|x: i32| {
257			x.wrapping_add(1)
258		}));
259		let id = empty::<Endofunction<RcFnBrand, i32>>();
260
261		let res = append(f.clone(), id);
262		res.0(val) == f.0(val)
263	}
264}