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