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}