semigroup/semigroup.rs
1use crate::Annotated;
2
3/// [`Semigroup`] represents a binary operation that satisfies the following properties
4/// 1. *Closure*: `op: T × T → T`
5/// 2. *Associativity*: `op(op(a, b), c) = op(a, op(b, c))`
6///
7/// # Examples
8/// ## Deriving
9/// When fields do not implement [`Semigroup`], use `with` attribute.
10/// ```
11/// use semigroup::Semigroup;
12/// #[derive(Debug, Clone, PartialEq, Semigroup)]
13/// #[semigroup(with = "semigroup::op::Coalesce")]
14/// pub struct ExampleStruct<'a> {
15/// pub str: Option<&'a str>,
16/// #[semigroup(with = "semigroup::op::Overwrite")]
17/// pub boolean: bool,
18/// #[semigroup(with = "semigroup::op::Sum")]
19/// pub sum: u32,
20/// }
21///
22/// let a = ExampleStruct { str: None, boolean: true, sum: 1 };
23/// let b = ExampleStruct { str: Some("ten"), boolean: false, sum: 10 };
24/// let c = ExampleStruct { str: None, boolean: false, sum: 100 };
25///
26/// // #[test]
27/// semigroup::assert_semigroup!(&a, &b, &c);
28/// assert_eq!(a.semigroup(b).semigroup(c), ExampleStruct { str: Some("ten"), boolean: false, sum: 111 });
29/// ```
30///
31/// ## Construction
32/// [`Semigroup`] can be constructed by [`crate::Construction`].
33///
34/// Some operations are already provided by [`crate::op`].
35/// ```
36/// use semigroup::{Construction, Semigroup};
37///
38/// #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default, Hash, Construction)]
39/// pub struct Sum(u64);
40/// impl Semigroup for Sum {
41/// fn op(base: Self, other: Self) -> Self {
42/// Self(base.0 + other.0)
43/// }
44/// }
45///
46/// let (a, b, c) = (Sum(1), Sum(2), Sum(3));
47/// // #[test]
48/// semigroup::assert_semigroup!(&a, &b, &c);
49/// assert_eq!(a.semigroup(b).semigroup(c), Sum(6));
50/// ```
51///
52/// # Testing
53/// Use [`crate::assert_semigroup!`] macro.
54///
55/// The *closure* property is guaranteed by Rust’s type system,
56/// but *associativity* must be verified manually using [`crate::assert_semigroup!`].
57pub trait Semigroup {
58 fn op(base: Self, other: Self) -> Self;
59 fn semigroup(self, other: Self) -> Self
60 where
61 Self: Sized,
62 {
63 Semigroup::op(self, other)
64 }
65}
66
67/// [`AnnotatedSemigroup`] is a [`Semigroup`] that has an annotation, such as [`crate::Annotate`].
68pub trait AnnotatedSemigroup<A>: Sized + Semigroup {
69 fn annotated_op(base: Annotated<Self, A>, other: Annotated<Self, A>) -> Annotated<Self, A>;
70}
71
72#[cfg(feature = "test")]
73pub mod test_semigroup {
74 use std::fmt::Debug;
75
76 use rand::seq::IndexedRandom;
77
78 use crate::{
79 combine::test_combine::{assert_combine_iter, assert_semigroup_reverse},
80 lazy::test_lazy::assert_lazy,
81 };
82
83 use super::*;
84
85 /// Assert that the given type satisfies the *semigroup* property.
86 ///
87 /// # Usage
88 /// ```sh
89 /// cargo add semigroup --dev --features test
90 /// ```
91 ///
92 /// - 1 argument: iterator of more than 3 items that implements [`Semigroup`].
93 /// - More than 3 arguments: items that implements [`Semigroup`].
94 ///
95 /// # Examples
96 /// ```
97 /// use semigroup::{assert_semigroup, op::Coalesce};
98 ///
99 /// let a = Coalesce(Some(1));
100 /// let b = Coalesce(None);
101 /// let c = Coalesce(Some(3));
102 /// assert_semigroup!(a, b, c);
103 ///
104 /// let v = vec![a, b, c];
105 /// assert_semigroup!(&v);
106 /// ```
107 ///
108 /// # Panics
109 /// - If the given function does not satisfy the *semigroup* property.
110 /// ```should_panic
111 /// use semigroup::{assert_semigroup, Construction, Semigroup};
112 /// #[derive(Debug, Clone, PartialEq, Construction)]
113 /// pub struct Sub(i32);
114 /// impl Semigroup for Sub {
115 /// fn op(base: Self, other: Self) -> Self {
116 /// Self(base.0 - other.0)
117 /// }
118 /// }
119 /// let a = Sub(1);
120 /// let b = Sub(2);
121 /// let c = Sub(3);
122 /// assert_semigroup!(a, b, c);
123 /// ```
124 ///
125 /// - The input iterator has less than 3 items.
126 /// ```compile_fail
127 /// use semigroup::{assert_semigroup, op::Coalesce};
128 /// let a = Coalesce(Some(1));
129 /// let b = Coalesce(None);
130 /// assert_semigroup!(a, b);
131 /// ```
132 /// ```should_panic
133 /// use semigroup::{assert_semigroup, op::Coalesce};
134 /// let a = Coalesce(Some(1));
135 /// let b = Coalesce(None);
136 /// assert_semigroup!(&vec![a, b]);
137 /// ```
138 #[macro_export]
139 macro_rules! assert_semigroup {
140 ($a:expr, $b: expr, $($tail: expr),*) => {
141 {
142 let v = vec![$a, $b, $($tail),*];
143 $crate::assert_semigroup!(&v)
144 }
145 };
146 ($v:expr) => {
147 {
148 let (a, b, c) = $crate::test_semigroup::pick3($v);
149 $crate::test_semigroup::assert_semigroup_impl(a.clone(), b.clone(), c.clone());
150 }
151 };
152 }
153
154 pub fn pick3<T: Clone>(data: &[T]) -> (T, T, T) {
155 data.choose_multiple_array(&mut rand::rng())
156 .map(|[a, b, c]| (a, b, c))
157 .expect("failed to pick 3 items")
158 }
159
160 pub fn assert_semigroup_impl<T: Semigroup + Clone + PartialEq + Debug>(a: T, b: T, c: T) {
161 assert_associative_law(a.clone(), b.clone(), c.clone());
162 assert_semigroup_reverse(a.clone(), b.clone(), c.clone());
163 assert_combine_iter(a.clone(), b.clone(), c.clone());
164 assert_lazy(a.clone(), b.clone(), c.clone());
165 #[cfg(feature = "monoid")]
166 crate::test_monoid::assert_option_monoid(a.clone(), b.clone(), c.clone());
167 }
168
169 pub fn assert_associative_law<T: Semigroup + Clone + PartialEq + Debug>(a: T, b: T, c: T) {
170 let ab_c = Semigroup::op(Semigroup::op(a.clone(), b.clone()), c.clone());
171 let a_bc = Semigroup::op(a.clone(), Semigroup::op(b.clone(), c.clone()));
172 assert_eq!(ab_c, a_bc);
173 }
174}