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/// # Deriving
8/// When fields do not implement [`Semigroup`], use `with` attribute.
9/// ```
10/// use semigroup::Semigroup;
11/// #[derive(Debug, Clone, PartialEq, Semigroup)]
12/// #[semigroup(with = "semigroup::op::Coalesce")]
13/// pub struct ExampleStruct<'a> {
14/// pub str: Option<&'a str>,
15/// #[semigroup(with = "semigroup::op::Overwrite")]
16/// pub boolean: bool,
17/// #[semigroup(with = "semigroup::op::Sum")]
18/// pub sum: u32,
19/// }
20///
21/// let a = ExampleStruct { str: None, boolean: true, sum: 1 };
22/// let b = ExampleStruct { str: Some("ten"), boolean: false, sum: 10 };
23/// let c = ExampleStruct { str: None, boolean: false, sum: 100 };
24///
25/// // #[test]
26/// semigroup::assert_semigroup!(&a, &b, &c);
27/// assert_eq!(a.semigroup(b).semigroup(c), ExampleStruct { str: Some("ten"), boolean: false, sum: 111 });
28/// ```
29///
30/// # Construction
31/// [`Semigroup`] can be constructed by [`crate::Construction`].
32///
33/// Some operations are already provided by [`crate::op`].
34/// ```
35/// use semigroup::{Construction, Semigroup};
36///
37/// #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default, Hash, Construction)]
38/// #[construction(monoid, commutative)]
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.
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(any(test, feature = "test"))]
73pub mod test_semigroup {
74 use std::fmt::Debug;
75
76 use rand::seq::IndexedRandom;
77
78 use crate::{
79 commutative::test_commutative::{assert_reverse_associative_law, assert_reverse_reverse},
80 iter::test_iter::assert_lazy_evaluation_iter,
81 };
82
83 use super::*;
84
85 /// Assert that the given type satisfies the *semigroup* property.
86 ///
87 /// # Usage
88 /// - 1 argument: iterator of more than 3 items that implements [`Semigroup`].
89 /// - More than 3 arguments: items that implements [`Semigroup`].
90 ///
91 /// # Examples
92 /// ```
93 /// use semigroup::{assert_semigroup, op::Coalesce};
94 ///
95 /// let a = Coalesce(Some(1));
96 /// let b = Coalesce(None);
97 /// let c = Coalesce(Some(3));
98 /// assert_semigroup!(a, b, c);
99 ///
100 /// let v = vec![a, b, c];
101 /// assert_semigroup!(&v);
102 /// ```
103 ///
104 /// # Panics
105 /// - If the given function does not satisfy the *semigroup* property.
106 /// - The input iterator has less than 3 items.
107 ///
108 /// ```compile_fail
109 /// use semigroup::{assert_semigroup, op::Coalesce};
110 /// let a = Coalesce(Some(1));
111 /// let b = Coalesce(None);
112 /// assert_semigroup!(a, b);
113 /// ```
114 #[macro_export]
115 macro_rules! assert_semigroup {
116 ($a:expr, $b: expr, $($tail: expr),*) => {
117 {
118 let v = vec![$a, $b, $($tail),*];
119 $crate::assert_semigroup!(&v)
120 }
121 };
122 ($v:expr) => {
123 {
124 let (a, b, c) = $crate::test_semigroup::pick3($v);
125 $crate::test_semigroup::assert_semigroup_impl(a.clone(), b.clone(), c.clone());
126 $crate::test_monoid::assert_option_monoid(a.clone(), b.clone(), c.clone());
127 }
128 };
129 }
130
131 pub fn pick3<T: Clone>(data: &[T]) -> (T, T, T) {
132 data.choose_multiple_array(&mut rand::rng())
133 .map(|[a, b, c]| (a, b, c))
134 .expect("failed to pick 3 items")
135 }
136
137 pub fn assert_semigroup_impl<T: Semigroup + Clone + PartialEq + Debug>(a: T, b: T, c: T) {
138 assert_associative_law(a.clone(), b.clone(), c.clone());
139 assert_reverse_reverse(a.clone(), b.clone(), c.clone());
140 assert_reverse_associative_law(a.clone(), b.clone(), c.clone());
141 assert_lazy_evaluation_iter(a.clone(), b.clone(), c.clone());
142 }
143
144 pub fn assert_associative_law<T: Semigroup + Clone + PartialEq + Debug>(a: T, b: T, c: T) {
145 let ab_c = T::op(T::op(a.clone(), b.clone()), c.clone());
146 let a_bc = T::op(a.clone(), T::op(b.clone(), c.clone()));
147 assert_eq!(ab_c, a_bc);
148 }
149}