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}