Skip to main content

fp_library/types/
endofunction.rs

1//! Wrapper for endofunctions (functions `a -> a`) with [`Semigroup`](crate::classes::Semigroup) and [`Monoid`](crate::classes::Monoid) instances based on function composition.
2//!
3//! Used to treat function composition as a monoidal operation where [`append`](crate::functions::append) composes functions and [`empty`](crate::functions::empty) is the identity function.
4
5#[fp_macros::document_module]
6mod inner {
7	use {
8		crate::{
9			classes::*,
10			functions::identity,
11		},
12		fp_macros::*,
13		std::{
14			fmt::{
15				self,
16				Debug,
17				Formatter,
18			},
19			hash::Hash,
20		},
21	};
22
23	/// A wrapper for endofunctions (functions from a set to the same set) that enables monoidal operations.
24	///
25	/// `Endofunction a` represents a function `a -> a`.
26	///
27	/// It exists to provide a monoid instance where:
28	///
29	/// * The binary operation [append][Semigroup::append] is [function composition][crate::functions::compose].
30	/// * The identity element [empty][Monoid::empty] is the [identity function][crate::functions::identity].
31	///
32	/// The wrapped function can be accessed directly via the [`.0` field][Endofunction#structfield.0].
33	#[document_type_parameters(
34		"The lifetime of the function and its captured data.",
35		"The brand of the cloneable function wrapper.",
36		"The input and output type of the function."
37	)]
38	///
39	pub struct Endofunction<'a, FnBrand: LiftFn, A: 'a>(
40		/// The wrapped function.
41		pub <FnBrand as CloneFn>::Of<'a, A, A>,
42	);
43
44	#[document_type_parameters(
45		"The lifetime of the function and its captured data.",
46		"The brand of the function (e.g., `RcFnBrand`).",
47		"The input and output type of the function."
48	)]
49	impl<'a, FnBrand: LiftFn, A: 'a> Endofunction<'a, FnBrand, A> {
50		/// Creates a new `Endofunction`.
51		///
52		/// This function wraps a function `a -> a` in an `Endofunction` struct.
53		#[document_signature]
54		///
55		#[document_parameters("The function to wrap.")]
56		///
57		#[document_returns("A new `Endofunction`.")]
58		///
59		#[document_examples]
60		///
61		/// ```
62		/// use fp_library::{
63		/// 	brands::*,
64		/// 	functions::*,
65		/// 	types::*,
66		/// };
67		///
68		/// let f = Endofunction::<RcFnBrand, _>::new(lift_fn_new::<RcFnBrand, _, _>(|x: i32| x * 2));
69		/// assert_eq!(f.0(5), 10);
70		/// ```
71		pub fn new(f: <FnBrand as CloneFn>::Of<'a, A, A>) -> Self {
72			Self(f)
73		}
74	}
75
76	#[document_type_parameters(
77		"The lifetime of the function and its captured data.",
78		"The brand of the function (e.g., `RcFnBrand`).",
79		"The input and output type of the function."
80	)]
81	#[document_parameters("The function to clone.")]
82	impl<'a, FnBrand: LiftFn, A: 'a> Clone for Endofunction<'a, FnBrand, A> {
83		#[document_signature]
84		#[document_returns("The cloned endofunction.")]
85		#[document_examples]
86		///
87		/// ```
88		/// use fp_library::{
89		/// 	brands::*,
90		/// 	functions::*,
91		/// 	types::*,
92		/// };
93		/// let f = Endofunction::<RcFnBrand, _>::new(lift_fn_new::<RcFnBrand, _, _>(|x: i32| x * 2));
94		/// let cloned = f.clone();
95		/// assert_eq!(cloned.0(5), 10);
96		/// ```
97		fn clone(&self) -> Self {
98			Self::new(self.0.clone())
99		}
100	}
101
102	#[document_type_parameters(
103		"The lifetime of the function and its captured data.",
104		"The brand of the function (e.g., `RcFnBrand`).",
105		"The input and output type of the function."
106	)]
107	#[document_parameters("The function to format.")]
108	impl<'a, FnBrand: LiftFn, A: 'a> Debug for Endofunction<'a, FnBrand, A>
109	where
110		<FnBrand as CloneFn>::Of<'a, A, A>: Debug,
111	{
112		#[document_signature]
113		#[document_parameters("The formatter to use.")]
114		#[document_returns("The result of the formatting operation.")]
115		#[document_examples]
116		///
117		/// ```
118		/// use fp_library::{
119		/// 	brands::*,
120		/// 	functions::*,
121		/// 	types::*,
122		/// };
123		/// let f = Endofunction::<RcFnBrand, _>::new(lift_fn_new::<RcFnBrand, _, _>(|x: i32| x * 2));
124		/// // Debug formatting is available when the inner function type implements Debug.
125		/// // Verify the endofunction applies correctly:
126		/// assert_eq!(f.0(5), 10);
127		/// ```
128		fn fmt(
129			&self,
130			fmt: &mut Formatter<'_>,
131		) -> fmt::Result {
132			fmt.debug_tuple("Endofunction").field(&self.0).finish()
133		}
134	}
135
136	#[document_type_parameters(
137		"The lifetime of the function and its captured data.",
138		"The brand of the function (e.g., `RcFnBrand`).",
139		"The input and output type of the function."
140	)]
141	impl<'a, FnBrand: LiftFn, A: 'a> Eq for Endofunction<'a, FnBrand, A> where
142		<FnBrand as CloneFn>::Of<'a, A, A>: Eq
143	{
144	}
145
146	#[document_type_parameters(
147		"The lifetime of the function and its captured data.",
148		"The brand of the function (e.g., `RcFnBrand`).",
149		"The input and output type of the function."
150	)]
151	#[document_parameters("The function to hash.")]
152	impl<'a, FnBrand: LiftFn, A: 'a> Hash for Endofunction<'a, FnBrand, A>
153	where
154		<FnBrand as CloneFn>::Of<'a, A, A>: Hash,
155	{
156		#[document_signature]
157		#[document_type_parameters("The type of the hasher.")]
158		#[document_parameters("The hasher state to update.")]
159		#[document_examples]
160		///
161		/// ```
162		/// use fp_library::{
163		/// 	brands::*,
164		/// 	functions::*,
165		/// 	types::*,
166		/// };
167		/// let f = Endofunction::<RcFnBrand, _>::new(lift_fn_new::<RcFnBrand, _, _>(|x: i32| x * 2));
168		/// // Hash is available when the inner function type implements Hash.
169		/// // Verify the endofunction applies correctly:
170		/// assert_eq!(f.0(5), 10);
171		/// ```
172		fn hash<H: std::hash::Hasher>(
173			&self,
174			state: &mut H,
175		) {
176			self.0.hash(state);
177		}
178	}
179
180	#[document_type_parameters(
181		"The lifetime of the function and its captured data.",
182		"The brand of the function (e.g., `RcFnBrand`).",
183		"The input and output type of the function."
184	)]
185	#[document_parameters("The function to compare.")]
186	impl<'a, FnBrand: LiftFn, A: 'a> Ord for Endofunction<'a, FnBrand, A>
187	where
188		<FnBrand as CloneFn>::Of<'a, A, A>: Ord,
189	{
190		#[document_signature]
191		#[document_parameters("The other function to compare to.")]
192		#[document_returns("The ordering of the values.")]
193		#[document_examples]
194		///
195		/// ```
196		/// use fp_library::{
197		/// 	brands::*,
198		/// 	functions::*,
199		/// 	types::*,
200		/// };
201		/// let f = Endofunction::<RcFnBrand, _>::new(lift_fn_new::<RcFnBrand, _, _>(|x: i32| x * 2));
202		/// let g = Endofunction::<RcFnBrand, _>::new(lift_fn_new::<RcFnBrand, _, _>(|x: i32| x * 2));
203		/// // Ord is available when the inner function type implements Ord.
204		/// // Both produce the same output for the same input:
205		/// assert_eq!(f.0(5), g.0(5));
206		/// ```
207		fn cmp(
208			&self,
209			other: &Self,
210		) -> std::cmp::Ordering {
211			self.0.cmp(&other.0)
212		}
213	}
214
215	#[document_type_parameters(
216		"The lifetime of the function and its captured data.",
217		"The brand of the function (e.g., `RcFnBrand`).",
218		"The input and output type of the function."
219	)]
220	#[document_parameters("The function to compare.")]
221	impl<'a, FnBrand: LiftFn, A: 'a> PartialEq for Endofunction<'a, FnBrand, A>
222	where
223		<FnBrand as CloneFn>::Of<'a, A, A>: PartialEq,
224	{
225		#[document_signature]
226		#[document_parameters("The other function to compare to.")]
227		#[document_returns("True if the values are equal, false otherwise.")]
228		#[document_examples]
229		///
230		/// ```
231		/// use fp_library::{
232		/// 	brands::*,
233		/// 	functions::*,
234		/// 	types::*,
235		/// };
236		/// let f = Endofunction::<RcFnBrand, _>::new(lift_fn_new::<RcFnBrand, _, _>(|x: i32| x * 2));
237		/// let g = Endofunction::<RcFnBrand, _>::new(lift_fn_new::<RcFnBrand, _, _>(|x: i32| x * 2));
238		/// // PartialEq is available when the inner function type implements PartialEq.
239		/// // Both produce the same output for the same input:
240		/// assert_eq!(f.0(5), g.0(5));
241		/// ```
242		fn eq(
243			&self,
244			other: &Self,
245		) -> bool {
246			self.0 == other.0
247		}
248	}
249
250	#[document_type_parameters(
251		"The lifetime of the function and its captured data.",
252		"The brand of the function (e.g., `RcFnBrand`).",
253		"The input and output type of the function."
254	)]
255	#[document_parameters("The function to compare.")]
256	impl<'a, FnBrand: LiftFn, A: 'a> PartialOrd for Endofunction<'a, FnBrand, A>
257	where
258		<FnBrand as CloneFn>::Of<'a, A, A>: PartialOrd,
259	{
260		#[document_signature]
261		#[document_parameters("The other function to compare to.")]
262		#[document_returns("An ordering if the values can be compared, none otherwise.")]
263		#[document_examples]
264		///
265		/// ```
266		/// use fp_library::{
267		/// 	brands::*,
268		/// 	functions::*,
269		/// 	types::*,
270		/// };
271		/// let f = Endofunction::<RcFnBrand, _>::new(lift_fn_new::<RcFnBrand, _, _>(|x: i32| x * 2));
272		/// let g = Endofunction::<RcFnBrand, _>::new(lift_fn_new::<RcFnBrand, _, _>(|x: i32| x * 2));
273		/// // PartialOrd is available when the inner function type implements PartialOrd.
274		/// // Both produce the same output for the same input:
275		/// assert_eq!(f.0(5), g.0(5));
276		/// ```
277		fn partial_cmp(
278			&self,
279			other: &Self,
280		) -> Option<std::cmp::Ordering> {
281			self.0.partial_cmp(&other.0)
282		}
283	}
284
285	#[document_type_parameters(
286		"The lifetime of the function and its captured data.",
287		"The brand of the function (e.g., `RcFnBrand`).",
288		"The input and output type of the function."
289	)]
290	impl<'a, FnBrand: 'a + LiftFn, A: 'a> Semigroup for Endofunction<'a, FnBrand, A> {
291		/// The result of combining the two values using the semigroup operation.
292		///
293		/// This method composes two endofunctions into a single endofunction.
294		/// Note that `Endofunction` composition is reversed relative to standard function composition:
295		/// `append(f, g)` results in `f . g` (read as "f after g"), meaning `g` is applied first, then `f`.
296		#[document_signature]
297		///
298		#[document_parameters(
299			"The second function to apply (the outer function).",
300			"The first function to apply (the inner function)."
301		)]
302		///
303		#[document_returns("The composed function `a . b`.")]
304		#[document_examples]
305		///
306		/// ```
307		/// use fp_library::{
308		/// 	brands::*,
309		/// 	functions::*,
310		/// 	types::*,
311		/// };
312		///
313		/// let f = Endofunction::<RcFnBrand, _>::new(lift_fn_new::<RcFnBrand, _, _>(|x: i32| x * 2));
314		/// let g = Endofunction::<RcFnBrand, _>::new(lift_fn_new::<RcFnBrand, _, _>(|x: i32| x + 1));
315		///
316		/// // f(g(x)) = (x + 1) * 2
317		/// let h = append::<_>(f, g);
318		/// assert_eq!(h.0(5), 12);
319		/// ```
320		fn append(
321			a: Self,
322			b: Self,
323		) -> Self {
324			let f = a.0;
325			let g = b.0;
326			// Compose: f . g
327			Self::new(<FnBrand as LiftFn>::new(move |x| f(g(x))))
328		}
329	}
330
331	#[document_type_parameters(
332		"The lifetime of the function and its captured data.",
333		"The brand of the function (e.g., `RcFnBrand`).",
334		"The input and output type of the function."
335	)]
336	impl<'a, FnBrand: 'a + LiftFn, A: 'a> Monoid for Endofunction<'a, FnBrand, A> {
337		/// The identity element.
338		///
339		/// This method returns the identity endofunction, which wraps the identity function.
340		#[document_signature]
341		///
342		#[document_returns("The identity endofunction.")]
343		///
344		#[document_examples]
345		///
346		/// ```
347		/// use fp_library::{
348		/// 	brands::*,
349		/// 	functions::*,
350		/// 	types::*,
351		/// };
352		///
353		/// let id = empty::<Endofunction<RcFnBrand, i32>>();
354		/// assert_eq!(id.0(5), 5);
355		/// ```
356		fn empty() -> Self {
357			Self::new(<FnBrand as LiftFn>::new(identity))
358		}
359	}
360}
361pub use inner::*;
362
363#[cfg(test)]
364mod tests {
365	use {
366		super::*,
367		crate::{
368			brands::RcFnBrand,
369			classes::*,
370			functions::*,
371		},
372		quickcheck_macros::quickcheck,
373	};
374
375	// Semigroup Laws
376
377	/// Tests the associativity law for Semigroup.
378	#[quickcheck]
379	fn semigroup_associativity(val: i32) -> bool {
380		let f = Endofunction::<RcFnBrand, _>::new(<RcFnBrand as LiftFn>::new(|x: i32| {
381			x.wrapping_add(1)
382		}));
383		let g = Endofunction::<RcFnBrand, _>::new(<RcFnBrand as LiftFn>::new(|x: i32| {
384			x.wrapping_mul(2)
385		}));
386		let h = Endofunction::<RcFnBrand, _>::new(<RcFnBrand as LiftFn>::new(|x: i32| {
387			x.wrapping_sub(3)
388		}));
389
390		let lhs = append(f.clone(), append(g.clone(), h.clone()));
391		let rhs = append(append(f, g), h);
392
393		lhs.0(val) == rhs.0(val)
394	}
395
396	// Monoid Laws
397
398	/// Tests the left identity law for Monoid.
399	#[quickcheck]
400	fn monoid_left_identity(val: i32) -> bool {
401		let f = Endofunction::<RcFnBrand, _>::new(<RcFnBrand as LiftFn>::new(|x: i32| {
402			x.wrapping_add(1)
403		}));
404		let id = empty::<Endofunction<RcFnBrand, i32>>();
405
406		let res = append(id, f.clone());
407		res.0(val) == f.0(val)
408	}
409
410	/// Tests the right identity law for Monoid.
411	#[quickcheck]
412	fn monoid_right_identity(val: i32) -> bool {
413		let f = Endofunction::<RcFnBrand, _>::new(<RcFnBrand as LiftFn>::new(|x: i32| {
414			x.wrapping_add(1)
415		}));
416		let id = empty::<Endofunction<RcFnBrand, i32>>();
417
418		let res = append(f.clone(), id);
419		res.0(val) == f.0(val)
420	}
421}