Skip to main content

acls_rs/algebra/
semigroup.rs

1//! Semigroup algebraic structure.
2//!
3//! A semigroup is a set equipped with an associative binary operation.
4//!
5//! # Algebraic Laws
6//!
7//! For a type to implement `Semigroup`, it must satisfy:
8//!
9//! - **Associativity**: `(a ∘ b) ∘ c = a ∘ (b ∘ c)` for all `a, b, c`
10//!
11//! # Examples
12//!
13//! ```
14//! use acls_rs::algebra::Semigroup;
15//!
16//! // PermissionSet implements Semigroup via union
17//! # use acls_rs::permission::{AtomicPermission, PermissionSet};
18//! let perms1 = PermissionSet::from([
19//!     AtomicPermission::new("file", "read"),
20//! ]);
21//! let perms2 = PermissionSet::from([
22//!     AtomicPermission::new("file", "write"),
23//! ]);
24//!
25//! let combined = perms1.combine(perms2);
26//! // combined contains both read and write
27//! ```
28
29use std::collections::BTreeSet;
30
31/// A semigroup: a set with an associative binary operation.
32///
33/// # Laws
34///
35/// Implementations must satisfy:
36/// - **Associativity**: `(a.combine(b)).combine(c) == a.combine(b.combine(c))`
37pub trait Semigroup: Sized {
38    /// Combine two elements associatively.
39    ///
40    /// This operation must be associative: `(a ∘ b) ∘ c = a ∘ (b ∘ c)`.
41    ///
42    /// # Examples
43    ///
44    /// ```
45    /// use acls_rs::algebra::Semigroup;
46    /// # use acls_rs::permission::{AtomicPermission, PermissionSet};
47    ///
48    /// let a = PermissionSet::from([AtomicPermission::new("a", "read")]);
49    /// let b = PermissionSet::from([AtomicPermission::new("b", "read")]);
50    /// let c = PermissionSet::from([AtomicPermission::new("c", "read")]);
51    ///
52    /// // Associativity
53    /// let left = a.clone().combine(b.clone()).combine(c.clone());
54    /// let right = a.combine(b.combine(c));
55    /// assert_eq!(left, right);
56    /// ```
57    fn combine(self, other: Self) -> Self;
58
59    /// Combine all elements from an iterator.
60    ///
61    /// Returns `None` if the iterator is empty, otherwise returns `Some` containing
62    /// the result of combining all elements.
63    ///
64    /// # Examples
65    ///
66    /// ```
67    /// use acls_rs::algebra::Semigroup;
68    /// # use acls_rs::permission::{AtomicPermission, PermissionSet};
69    ///
70    /// let sets = vec![
71    ///     PermissionSet::from([AtomicPermission::new("a", "read")]),
72    ///     PermissionSet::from([AtomicPermission::new("b", "read")]),
73    ///     PermissionSet::from([AtomicPermission::new("c", "read")]),
74    /// ];
75    ///
76    /// let combined = PermissionSet::combine_all(sets).unwrap();
77    /// assert_eq!(combined.len(), 3);
78    /// ```
79    fn combine_all<I: IntoIterator<Item = Self>>(iter: I) -> Option<Self> {
80        iter.into_iter().reduce(|a, b| a.combine(b))
81    }
82}
83
84// Blanket implementation for BTreeSet (used internally by PermissionSet)
85impl<T: Ord + Clone> Semigroup for BTreeSet<T> {
86    #[inline]
87    fn combine(self, other: Self) -> Self {
88        self.union(&other).cloned().collect()
89    }
90}
91
92#[cfg(test)]
93mod tests {
94    use super::*;
95
96    #[test]
97    fn test_btreeset_semigroup_associativity() {
98        let a: BTreeSet<i32> = [1, 2].iter().cloned().collect();
99        let b: BTreeSet<i32> = [2, 3].iter().cloned().collect();
100        let c: BTreeSet<i32> = [3, 4].iter().cloned().collect();
101
102        let left = a.clone().combine(b.clone()).combine(c.clone());
103        let right = a.combine(b.combine(c));
104
105        assert_eq!(left, right);
106    }
107
108    #[test]
109    fn test_combine_all() {
110        let sets = vec![
111            [1, 2].iter().cloned().collect::<BTreeSet<_>>(),
112            [3, 4].iter().cloned().collect::<BTreeSet<_>>(),
113            [5].iter().cloned().collect::<BTreeSet<_>>(),
114        ];
115
116        let combined = BTreeSet::combine_all(sets).unwrap();
117        assert_eq!(combined, [1, 2, 3, 4, 5].iter().cloned().collect());
118    }
119
120    #[test]
121    fn test_combine_all_empty() {
122        let empty: Vec<BTreeSet<i32>> = vec![];
123        assert_eq!(BTreeSet::combine_all(empty), None);
124    }
125}