Skip to main content

karpal_core/
semigroup.rs

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