Skip to main content

fp_library/types/
endofunction.rs

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