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