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