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