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