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}