Skip to main content

karpal_core/
semigroup.rs

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