1#[cfg(all(not(feature = "std"), feature = "alloc"))]
2use alloc::{string::String, vec::Vec};
3
4pub 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 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}