fp_library/types/
endofunction.rs

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