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}