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