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 /// # Type Signature
29 ///
30 /// `forall a. Semigroup a => a -> a -> a`
31 ///
32 /// # Parameters
33 ///
34 /// * `a`: First value to combine.
35 /// * `b`: Second value to combine.
36 ///
37 /// # Returns
38 ///
39 /// The result of combining the two values using the semigroup operation.
40 fn append(a: Apply<Self, ()>) -> impl Fn(Apply<Self, ()>) -> Apply<Self, ()>;
41}
42
43/// Associative operation that combines two values of the same type.
44///
45/// Free function version that dispatches to [the typeclass method][`Semigroup::append`].
46///
47/// # Type Signature
48///
49/// `forall a. Semigroup a => a -> a -> a`
50///
51/// # Parameters
52///
53/// * `a`: First value to combine.
54/// * `b`: Second value to combine.
55///
56/// # Returns
57///
58/// The result of combining the two values using the semigroup operation.
59///
60/// # Examples
61///
62/// ```
63/// use fp_library::{brands::StringBrand, functions::append};
64///
65/// assert_eq!(
66/// append::<StringBrand>("Hello, ".to_string())("World!".to_string()),
67/// "Hello, World!"
68/// );
69/// ```
70pub fn append<Brand>(a: Apply<Brand, ()>) -> impl Fn(Apply<Brand, ()>) -> Apply<Brand, ()>
71where
72 Brand: Kind<()> + Semigroup,
73{
74 Brand::append(a)
75}
76
77#[cfg(test)]
78mod tests {
79 use crate::{brands::StringBrand, functions::append};
80
81 #[test]
82 fn test_string_semigroup() {
83 let s1 = "Hello, ".to_string();
84 let s2 = "World!".to_string();
85 assert_eq!(append::<StringBrand>(s1)(s2), "Hello, World!");
86 }
87
88 #[test]
89 fn test_string_semigroup_associativity() {
90 let s1 = "a".to_string();
91 let s2 = "b".to_string();
92 let s3 = "c".to_string();
93
94 // (a <> b) <> c = a <> (b <> c)
95 let left_associated =
96 append::<StringBrand>(append::<StringBrand>(s1.clone())(s2.clone()))(s3.clone());
97 let right_associated =
98 append::<StringBrand>(s1.clone())(append::<StringBrand>(s2.clone())(s3.clone()));
99
100 assert_eq!(left_associated, right_associated);
101 assert_eq!(left_associated, "abc");
102 }
103}