Skip to main content

karpal_core/
semigroup.rs

1/// A type with an associative binary operation.
2pub trait Semigroup {
3    fn combine(self, other: Self) -> Self;
4}
5
6macro_rules! impl_additive_semigroup {
7    ($($t:ty),*) => {
8        $(
9            impl Semigroup for $t {
10                fn combine(self, other: Self) -> Self {
11                    self + other
12                }
13            }
14        )*
15    };
16}
17
18impl_additive_semigroup!(i8, i16, i32, i64, i128, u8, u16, u32, u64, u128, f32, f64);
19
20#[cfg(any(feature = "std", feature = "alloc"))]
21impl Semigroup for String {
22    fn combine(mut self, other: Self) -> Self {
23        self.push_str(&other);
24        self
25    }
26}
27
28#[cfg(any(feature = "std", feature = "alloc"))]
29impl<T> Semigroup for Vec<T> {
30    fn combine(mut self, other: Self) -> Self {
31        self.extend(other);
32        self
33    }
34}
35
36impl<T: Semigroup> Semigroup for Option<T> {
37    fn combine(self, other: Self) -> Self {
38        match (self, other) {
39            (Some(a), Some(b)) => Some(a.combine(b)),
40            (a @ Some(_), None) => a,
41            (None, b) => b,
42        }
43    }
44}
45
46#[cfg(any(feature = "std", feature = "alloc"))]
47impl<T> Semigroup for crate::hkt::NonEmptyVec<T> {
48    fn combine(mut self, other: Self) -> Self {
49        self.tail.push(other.head);
50        self.tail.extend(other.tail);
51        self
52    }
53}
54
55impl<A: Semigroup, B: Semigroup> Semigroup for (A, B) {
56    fn combine(self, other: Self) -> Self {
57        (self.0.combine(other.0), self.1.combine(other.1))
58    }
59}
60
61#[cfg(test)]
62mod tests {
63    use super::*;
64
65    #[test]
66    fn i32_combine() {
67        assert_eq!(3i32.combine(4), 7);
68    }
69
70    #[test]
71    fn string_combine() {
72        assert_eq!(
73            "hello ".to_string().combine("world".to_string()),
74            "hello world"
75        );
76    }
77
78    #[test]
79    fn vec_combine() {
80        assert_eq!(vec![1, 2].combine(vec![3, 4]), vec![1, 2, 3, 4]);
81    }
82
83    #[test]
84    fn option_combine() {
85        assert_eq!(Some(3i32).combine(Some(4)), Some(7));
86        assert_eq!(Some(3i32).combine(None), Some(3));
87        assert_eq!(None::<i32>.combine(Some(4)), Some(4));
88        assert_eq!(None::<i32>.combine(None), None);
89    }
90}
91
92#[cfg(test)]
93mod law_tests {
94    use super::*;
95    use proptest::prelude::*;
96
97    proptest! {
98        #[test]
99        fn i32_associativity(a in any::<i32>(), b in any::<i32>(), c in any::<i32>()) {
100            // Use wrapping to avoid overflow panics
101            let left = (a.wrapping_add(b)).wrapping_add(c);
102            let right = a.wrapping_add(b.wrapping_add(c));
103            prop_assert_eq!(left, right);
104        }
105
106        #[test]
107        fn string_associativity(
108            a in "[a-z]{0,10}",
109            b in "[a-z]{0,10}",
110            c in "[a-z]{0,10}"
111        ) {
112            let left = a.clone().combine(b.clone()).combine(c.clone());
113            let right = a.combine(b.combine(c));
114            prop_assert_eq!(left, right);
115        }
116
117        #[test]
118        fn vec_associativity(
119            a in prop::collection::vec(any::<i32>(), 0..5),
120            b in prop::collection::vec(any::<i32>(), 0..5),
121            c in prop::collection::vec(any::<i32>(), 0..5)
122        ) {
123            let left = a.clone().combine(b.clone()).combine(c.clone());
124            let right = a.combine(b.combine(c));
125            prop_assert_eq!(left, right);
126        }
127
128        #[test]
129        fn option_associativity(
130            a in proptest::option::of(0u16..100),
131            b in proptest::option::of(0u16..100),
132            c in proptest::option::of(0u16..100)
133        ) {
134            let left = a.clone().combine(b.clone()).combine(c.clone());
135            let right = a.combine(b.combine(c));
136            prop_assert_eq!(left, right);
137        }
138    }
139}