fp_library/typeclasses/
semigroup.rs

1use crate::hkt::{Apply, Kind};
2
3/// A typeclass for semigroups.
4///
5/// A `Semigroup` is a set equipped with an associative binary operation.
6/// This means for any elements `a`, `b`, and `c` in the set, the operation
7/// satisfies: `(a <> b) <> c = a <> (b <> c)`.
8///
9/// In functional programming, semigroups are useful for combining values
10/// in a consistent way. They form the basis for more complex structures
11/// like monoids.
12///
13/// # Laws
14///
15/// Semigroup instances must satisfy the associative law:
16/// * Associativity: `append(append(x, y), z) = append(x, append(y, z))`
17///
18/// # Examples
19///
20/// Common semigroups include:
21/// * Strings with concatenation.
22/// * Numbers with addition.
23/// * Numbers with multiplication.
24/// * Lists with concatenation.
25pub trait Semigroup: Kind<()> {
26	/// Associative operation that combines two values of the same type.
27	///
28	/// # Parameters
29	///
30	/// * `a`: First value to combine.
31	///
32	/// # Returns
33	///
34	/// A function that takes in the second value to combine and returns
35	/// the result of combining the two values using the semigroup operation.
36	///
37	/// # Type Signature
38	///
39	/// `forall a. Semigroup a => a -> a -> a`
40	fn append(a: Apply<Self, ()>) -> impl Fn(Apply<Self, ()>) -> Apply<Self, ()>
41	where
42		Apply<Self, ()>: Clone;
43}
44
45/// Associative operation that combines two values of the same type.
46///
47/// Free function version that dispatches to [the typeclass method][`Semigroup::append`].
48///
49/// # Type Signature
50///
51/// `forall a. Semigroup a => a -> a -> a`
52///
53/// # Examples
54///
55/// ```
56/// use fp_library::{brands::StringBrand, functions::append};
57///
58/// assert_eq!(
59///     append::<StringBrand>("Hello, ".to_string())("World!".to_string()),
60///     "Hello, World!"
61/// );
62/// ```
63pub fn append<Brand>(a: Apply<Brand, ()>) -> impl Fn(Apply<Brand, ()>) -> Apply<Brand, ()>
64where
65	Brand: Kind<()> + Semigroup,
66	Apply<Brand, ()>: Clone,
67{
68	move |b| Brand::append(a.to_owned())(b)
69}
70
71#[cfg(test)]
72mod tests {
73	use crate::{brands::StringBrand, functions::append};
74
75	#[test]
76	fn test_string_semigroup() {
77		let s1 = "Hello, ".to_string();
78		let s2 = "World!".to_string();
79		assert_eq!(append::<StringBrand>(s1)(s2), "Hello, World!");
80	}
81
82	#[test]
83	fn test_string_semigroup_associativity() {
84		let s1 = "a".to_string();
85		let s2 = "b".to_string();
86		let s3 = "c".to_string();
87
88		// (a <> b) <> c = a <> (b <> c)
89		let left_associated =
90			append::<StringBrand>(append::<StringBrand>(s1.clone())(s2.clone()))(s3.clone());
91		let right_associated =
92			append::<StringBrand>(s1.clone())(append::<StringBrand>(s2.clone())(s3.clone()));
93
94		assert_eq!(left_associated, right_associated);
95		assert_eq!(left_associated, "abc");
96	}
97}