algebra/
lib.rs

1/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 * All rights reserved.
4 *
5 * This source code is licensed under the BSD-style license found in the
6 * LICENSE file in the root directory of this source tree.
7 */
8
9#![deny(missing_docs)]
10
11//! Algebraic structures for monotonic accumulation and lattice-based
12//! reduction.
13//!
14//! This module provides foundational traits and types for expressing
15//! commutative, associative, and idempotent operations, enabling
16//! principled accumulation in distributed systems.
17//!
18//! # Quick Start
19//!
20//! ```rust
21//! use algebra::BoundedJoinSemilattice;
22//! use algebra::JoinSemilattice;
23//! use algebra::Max;
24//!
25//! // Max<T> wraps an ordered type where join = max
26//! let a = Max(5);
27//! let b = Max(3);
28//! let c = a.join(&b);
29//! assert_eq!(c, Max(5));
30//!
31//! // Bottom element is the identity for join
32//! assert_eq!(Max::<i32>::bottom().join(&a), a);
33//! ```
34//!
35//! # Core Concepts
36//!
37//! - **Semigroup**: A type with an associative binary operation
38//!   (`combine`). Examples: addition, max, string concatenation.
39//!
40//! - **Monoid**: A semigroup with an identity element (`empty`).
41//!   Examples: 0 for addition, empty string for concatenation.
42//!
43//! - **CommutativeMonoid**: A monoid where combine is commutative.
44//!
45//! - **Group**: A monoid where every element has an inverse.
46//!
47//! - **AbelianGroup**: A commutative group.
48//!
49//! - **JoinSemilattice**: A commutative, associative, and
50//!   **idempotent** merge operation (`join`). You can think of `join`
51//!   as computing a **least upper bound**: it produces the smallest
52//!   value that is "at least as large as" both inputs under the order
53//!   induced by join (`a ≤ b` iff `a.join(&b) == b`). Idempotence (`a
54//!   ⊔ a = a`) is what makes this safe under at-least-once delivery:
55//!   re-merging the same update is a no-op. Examples: max, min, set
56//!   union.
57//!
58//! - **BoundedJoinSemilattice**: A join-semilattice with an explicit
59//!   bottom element (⊥) that serves as the identity for join.
60//!
61//! # Homomorphisms
62//!
63//! - **SemigroupHom**: A structure-preserving map between semigroups.
64//! - **MonoidHom**: A structure-preserving map between monoids.
65//!
66//! # Provided Types
67//!
68//! - [`Max<T>`]: Ordered type where `join = max`, bottom = minimum.
69//! - [`Min<T>`]: Ordered type where `join = min`, bottom = maximum.
70//! - [`Any`]: Boolean where `join = ||` (OR), bottom = false.
71//! - [`All`]: Boolean where `join = &&` (AND), bottom = true.
72//! - [`LatticeMap<K, V>`]: Pointwise map lattice over `HashMap`.
73//! - [`LWW<T>`]: Last-Writer-Wins register CRDT.
74//!
75//! # Why Idempotence Matters for Distributed Systems
76//!
77//! In distributed systems with at-least-once delivery, messages may
78//! be delivered multiple times due to retries, network partitions,
79//! or failover. Non-idempotent operations (like addition) produce
80//! incorrect results when applied multiple times:
81//!
82//! ```text
83//! // Problem: sum accumulator with duplicates
84//! sum(1, 2, 2, 3)  // Intended: 1+2+3=6, Actual: 1+2+2+3=8 ❌
85//!
86//! // Solution: max accumulator is idempotent
87//! max(1, 2, 2, 3)  // Always 3, regardless of duplicates ✓
88//! ```
89//!
90//! By using lattice types like [`Max`] and [`Min`], we make the
91//! idempotence guarantee explicit in the type system.
92//!
93//! # Examples
94//!
95//! ```
96//! use algebra::BoundedJoinSemilattice;
97//! use algebra::JoinSemilattice;
98//! use algebra::Max;
99//!
100//! let a = Max(5);
101//! let b = Max(10);
102//! let c = a.join(&b);
103//! assert_eq!(c, Max(10));
104//!
105//! // Idempotence: joining with self has no effect
106//! assert_eq!(c.join(&c), c);
107//!
108//! // Identity: joining with bottom has no effect
109//! assert_eq!(c.join(&Max::bottom()), c);
110//! ```
111
112mod crdt;
113mod join_semilattice;
114
115// Re-export CRDTs
116pub use crdt::LWW;
117// Re-export concrete lattice types
118pub use join_semilattice::All;
119pub use join_semilattice::Any;
120pub use join_semilattice::LatticeMap;
121pub use join_semilattice::Max;
122pub use join_semilattice::Min;
123
124// Semigroup
125
126/// A **semigroup**: a type with an associative binary operation.
127///
128/// Laws (not enforced by type system):
129///
130/// - **Associative**:
131///   `a.combine(b).combine(c) == a.combine(b.combine(c))`
132///
133/// # Example
134///
135/// ```rust
136/// use algebra::Semigroup;
137///
138/// #[derive(Clone, Copy, Debug, PartialEq, Eq)]
139/// struct Max(i32);
140///
141/// impl Semigroup for Max {
142///     fn combine(&self, other: &Self) -> Self {
143///         Max(self.0.max(other.0))
144///     }
145/// }
146///
147/// let x = Max(3);
148/// let y = Max(5);
149/// let z = Max(2);
150/// assert_eq!(x.combine(&y).combine(&z), x.combine(&y.combine(&z)));
151/// ```
152pub trait Semigroup: Sized {
153    /// Combine two elements associatively.
154    fn combine(&self, other: &Self) -> Self;
155
156    /// In-place combine.
157    fn combine_assign(&mut self, other: &Self) {
158        *self = self.combine(other);
159    }
160}
161
162// Monoid
163
164/// A **monoid**: a semigroup with an identity element.
165///
166/// Laws (not enforced by type system):
167///
168/// - **Associative**:
169///   `a.combine(b).combine(c) == a.combine(b.combine(c))`
170/// - **Left identity**: `empty().combine(a) == a`
171/// - **Right identity**: `a.combine(empty()) == a`
172///
173/// # Example
174///
175/// ```rust
176/// use algebra::Monoid;
177/// use algebra::Semigroup;
178///
179/// #[derive(Clone, Copy, Debug, PartialEq, Eq)]
180/// struct Product(i32);
181///
182/// impl Semigroup for Product {
183///     fn combine(&self, other: &Self) -> Self {
184///         Product(self.0 * other.0)
185///     }
186/// }
187///
188/// impl Monoid for Product {
189///     fn empty() -> Self {
190///         Product(1)
191///     }
192/// }
193///
194/// let x = Product(3);
195/// let y = Product(5);
196/// assert_eq!(x.combine(&y), Product(15));
197/// assert_eq!(Product::empty().combine(&x), x);
198/// assert_eq!(x.combine(&Product::empty()), x);
199/// ```
200pub trait Monoid: Semigroup {
201    /// The identity element.
202    fn empty() -> Self;
203
204    /// Fold an iterator using combine, starting from empty.
205    fn concat<I>(iter: I) -> Self
206    where
207        I: IntoIterator<Item = Self>,
208    {
209        iter.into_iter()
210            .fold(Self::empty(), |acc, x| acc.combine(&x))
211    }
212}
213
214// CommutativeMonoid
215
216/// A **commutative monoid**: a monoid where combine is commutative.
217///
218/// Laws (not enforced by type system):
219///
220/// - **Associative**:
221///   `a.combine(b).combine(c) == a.combine(b.combine(c))`
222/// - **Commutative**: `a.combine(b) == b.combine(a)`
223/// - **Identity**: `a.combine(empty()) == a == empty().combine(a)`
224pub trait CommutativeMonoid: Monoid {}
225
226// Group
227
228/// A **group**: a monoid where every element has an inverse.
229///
230/// Laws (not enforced by type system):
231///
232/// - **Associative**: `a.combine(b).combine(c) == a.combine(b.combine(c))`
233/// - **Identity**: `a.combine(empty()) == a == empty().combine(a)`
234/// - **Inverse**: `a.combine(a.inverse()) == empty() ==
235///   a.inverse().combine(a)`
236///
237/// # Example
238///
239/// ```rust
240/// use algebra::Group;
241/// use algebra::Monoid;
242/// use algebra::Semigroup;
243///
244/// #[derive(Clone, Copy, Debug, PartialEq, Eq)]
245/// struct AddInt(i32);
246///
247/// impl Semigroup for AddInt {
248///     fn combine(&self, other: &Self) -> Self {
249///         AddInt(self.0 + other.0)
250///     }
251/// }
252///
253/// impl Monoid for AddInt {
254///     fn empty() -> Self {
255///         AddInt(0)
256///     }
257/// }
258///
259/// impl Group for AddInt {
260///     fn inverse(&self) -> Self {
261///         AddInt(-self.0)
262///     }
263/// }
264///
265/// let x = AddInt(5);
266/// let inv = x.inverse();
267/// assert_eq!(x.combine(&inv), AddInt::empty());
268/// ```
269pub trait Group: Monoid {
270    /// Return the inverse of this element.
271    fn inverse(&self) -> Self;
272}
273
274// AbelianGroup
275
276/// An **abelian group** (commutative group): a group where combine
277/// is commutative.
278///
279/// Laws (not enforced by type system):
280///
281/// - **Associative**: `a.combine(b).combine(c) == a.combine(b.combine(c))`
282/// - **Commutative**: `a.combine(b) == b.combine(a)`
283/// - **Identity**: `a.combine(empty()) == a == empty().combine(a)`
284/// - **Inverse**: `a.combine(a.inverse()) == empty() ==
285///   a.inverse().combine(a)`
286///
287/// Named after mathematician Niels Henrik Abel.
288pub trait AbelianGroup: Group + CommutativeMonoid {}
289
290// SemigroupHom
291
292/// A **semigroup homomorphism**: a structure-preserving map between
293/// semigroups that preserves combine.
294///
295/// A homomorphism `f: S → T` preserves the semigroup operation:
296///
297/// Laws (not enforced by type system):
298///
299/// - **Preserve combine**: `f(x.combine(y)) == f(x).combine(f(y))`
300///
301/// # Example
302///
303/// ```rust
304/// use algebra::Max;
305/// use algebra::Semigroup;
306/// use algebra::SemigroupHom;
307///
308/// // A homomorphism from Max<i32> to Max<i32> that doubles the value
309/// struct Double;
310///
311/// impl SemigroupHom for Double {
312///     type Source = Max<i32>;
313///     type Target = Max<i32>;
314///
315///     fn apply(&self, x: &Max<i32>) -> Max<i32> {
316///         Max(x.0 * 2)
317///     }
318/// }
319///
320/// // Verify: f(x ⊔ y) = f(x) ⊔ f(y)
321/// // Since join = max: f(max(a, b)) = max(f(a), f(b))
322/// let f = Double;
323/// let x = Max(3);
324/// let y = Max(5);
325/// assert_eq!(f.apply(&x.combine(&y)), f.apply(&x).combine(&f.apply(&y)));
326/// ```
327pub trait SemigroupHom {
328    /// The source semigroup
329    type Source: Semigroup;
330
331    /// The target semigroup
332    type Target: Semigroup;
333
334    /// Apply the homomorphism
335    fn apply(&self, x: &Self::Source) -> Self::Target;
336}
337
338/// Helper trait for explicitly specifying source and target types.
339///
340/// This is a blanket-implemented alias that allows writing
341/// `T: SemigroupHomFromTo<S, T>` instead of
342/// `T: SemigroupHom<Source = S, Target = T>`.
343pub trait SemigroupHomFromTo<S: Semigroup, T: Semigroup>:
344    SemigroupHom<Source = S, Target = T>
345{
346}
347
348impl<H, S, T> SemigroupHomFromTo<S, T> for H
349where
350    H: SemigroupHom<Source = S, Target = T>,
351    S: Semigroup,
352    T: Semigroup,
353{
354}
355
356// MonoidHom
357
358/// A **monoid homomorphism**: a structure-preserving map between
359/// monoids that preserves both combine and identity.
360///
361/// A homomorphism `f: M → N` preserves both the monoid operation and
362/// identity:
363///
364/// Laws (not enforced by type system):
365///
366/// - **Preserve combine**: `f(x.combine(y)) == f(x).combine(f(y))`
367/// - **Preserve identity**: `f(M::empty()) == N::empty()`
368///
369/// # Example
370///
371/// ```rust
372/// use algebra::Max;
373/// use algebra::Monoid;
374/// use algebra::MonoidHom;
375/// use algebra::Semigroup;
376/// use algebra::SemigroupHom;
377///
378/// // A monoid homomorphism that widens Max<u32> to Max<u64>.
379/// // This preserves identity because bottom for both is 0.
380/// struct Widen;
381///
382/// impl SemigroupHom for Widen {
383///     type Source = Max<u32>;
384///     type Target = Max<u64>;
385///
386///     fn apply(&self, x: &Max<u32>) -> Max<u64> {
387///         Max(x.0 as u64)
388///     }
389/// }
390///
391/// impl MonoidHom for Widen {}
392///
393/// // Verify identity preservation: f(⊥) = ⊥
394/// let widen = Widen;
395/// assert_eq!(widen.apply(&Max::<u32>::empty()), Max::<u64>::empty());
396///
397/// // Verify combine preservation
398/// let a = Max(10u32);
399/// let b = Max(20u32);
400/// assert_eq!(
401///     widen.apply(&a.combine(&b)),
402///     widen.apply(&a).combine(&widen.apply(&b))
403/// );
404/// ```
405pub trait MonoidHom: SemigroupHom {}
406
407/// Helper trait for explicitly specifying source and target monoids.
408///
409/// This is a blanket-implemented alias that allows writing
410/// `T: MonoidHomFromTo<M, N>` instead of
411/// `T: MonoidHom<Source = M, Target = N>`.
412pub trait MonoidHomFromTo<M: Monoid, N: Monoid>: MonoidHom<Source = M, Target = N> {}
413
414impl<H, M, N> MonoidHomFromTo<M, N> for H
415where
416    H: MonoidHom<Source = M, Target = N>,
417    M: Monoid,
418    N: Monoid,
419{
420}
421
422// JoinSemilattice
423
424/// A **join-semilattice**: a type with an associative, commutative,
425/// and idempotent binary operation (the join).
426///
427/// Laws (not enforced by type system):
428///
429/// - **Associative**: `a.join(b).join(c) == a.join(b.join(c))`
430/// - **Commutative**: `a.join(b) == b.join(a)`
431/// - **Idempotent**: `a.join(a) == a`
432///
433/// The `join` operation computes the least upper bound (supremum) in
434/// the induced partial order: `x ≤ y` iff `x.join(y) == y`.
435///
436/// # Example
437///
438/// ```rust
439/// use algebra::JoinSemilattice;
440/// use algebra::Max;
441///
442/// let a = Max(3);
443/// let b = Max(5);
444///
445/// // join = max
446/// let c = a.join(&b);
447/// assert_eq!(c, Max(5));
448///
449/// // Idempotent
450/// assert_eq!(a.join(&a), a);
451/// ```
452pub trait JoinSemilattice: Sized {
453    /// The join (least upper bound).
454    fn join(&self, other: &Self) -> Self;
455
456    /// In-place variant.
457    fn join_assign(&mut self, other: &Self) {
458        *self = self.join(other);
459    }
460
461    /// Derived partial order: x ≤ y iff x ⊔ y = y.
462    fn leq(&self, other: &Self) -> bool
463    where
464        Self: PartialEq,
465    {
466        self.join(other) == *other
467    }
468
469    /// Join a finite iterator of values. Returns `None` for empty
470    /// iterators.
471    fn join_all<I>(it: I) -> Option<Self>
472    where
473        I: IntoIterator<Item = Self>,
474    {
475        it.into_iter().reduce(|acc, x| acc.join(&x))
476    }
477}
478
479// BoundedJoinSemilattice
480
481/// A **bounded join-semilattice**: a join-semilattice with a bottom
482/// element that serves as the identity for join.
483///
484/// Laws (not enforced by type system):
485///
486/// - **Associative**: `a.join(b).join(c) == a.join(b.join(c))`
487/// - **Commutative**: `a.join(b) == b.join(a)`
488/// - **Idempotent**: `a.join(a) == a`
489/// - **Identity**: `bottom().join(a) == a == a.join(bottom())`
490///
491/// The bottom element (⊥) is the least element in the partial order.
492///
493/// # Example
494///
495/// ```rust
496/// use algebra::BoundedJoinSemilattice;
497/// use algebra::JoinSemilattice;
498/// use algebra::Max;
499///
500/// let a = Max(10);
501///
502/// // bottom = minimum value
503/// let bottom = Max::<i32>::bottom();
504///
505/// // Identity law
506/// assert_eq!(bottom.join(&a), a);
507/// assert_eq!(a.join(&bottom), a);
508/// ```
509pub trait BoundedJoinSemilattice: JoinSemilattice {
510    /// The bottom element of the lattice (⊥).
511    ///
512    /// This is the least element w.r.t. the induced partial order: for
513    /// all `x`, `bottom().join(x) == x`.
514    fn bottom() -> Self;
515
516    /// Join a finite iterator of values, starting from ⊥.
517    ///
518    /// Never returns `None`: an empty iterator produces `bottom()`.
519    fn join_all_from_bottom<I>(it: I) -> Self
520    where
521        I: IntoIterator<Item = Self>,
522    {
523        it.into_iter().fold(Self::bottom(), |acc, x| acc.join(&x))
524    }
525}
526
527// Blanket implementations: JoinSemilattice provides Semigroup/Monoid
528
529impl<T: JoinSemilattice> Semigroup for T {
530    fn combine(&self, other: &Self) -> Self {
531        self.join(other)
532    }
533
534    fn combine_assign(&mut self, other: &Self) {
535        self.join_assign(other);
536    }
537}
538
539impl<T: BoundedJoinSemilattice> Monoid for T {
540    fn empty() -> Self {
541        Self::bottom()
542    }
543
544    fn concat<I>(iter: I) -> Self
545    where
546        I: IntoIterator<Item = Self>,
547    {
548        Self::join_all_from_bottom(iter)
549    }
550}
551
552impl<T: BoundedJoinSemilattice> CommutativeMonoid for T {}
553
554// Tests
555
556#[cfg(test)]
557mod tests {
558    use super::*;
559
560    #[derive(Clone, Copy, Debug, PartialEq, Eq)]
561    struct Sum(i32);
562
563    impl Semigroup for Sum {
564        fn combine(&self, other: &Self) -> Self {
565            Sum(self.0 + other.0)
566        }
567    }
568
569    impl Monoid for Sum {
570        fn empty() -> Self {
571            Sum(0)
572        }
573    }
574
575    impl CommutativeMonoid for Sum {}
576
577    #[test]
578    fn semigroup_combine_works() {
579        let x = Sum(3);
580        let y = Sum(5);
581        assert_eq!(x.combine(&y), Sum(8));
582    }
583
584    #[test]
585    fn semigroup_is_associative() {
586        let x = Sum(1);
587        let y = Sum(2);
588        let z = Sum(3);
589        assert_eq!(x.combine(&y).combine(&z), x.combine(&y.combine(&z)));
590    }
591
592    #[test]
593    fn monoid_has_identity() {
594        let x = Sum(5);
595        assert_eq!(Sum::empty().combine(&x), x);
596        assert_eq!(x.combine(&Sum::empty()), x);
597    }
598
599    #[test]
600    fn monoid_concat_works() {
601        let values = vec![Sum(1), Sum(2), Sum(3)];
602        assert_eq!(Sum::concat(values), Sum(6));
603    }
604
605    #[test]
606    fn monoid_concat_empty_is_identity() {
607        let empty: Vec<Sum> = vec![];
608        assert_eq!(Sum::concat(empty), Sum::empty());
609    }
610
611    #[test]
612    fn commutative_monoid_is_commutative() {
613        let x = Sum(3);
614        let y = Sum(5);
615        assert_eq!(x.combine(&y), y.combine(&x));
616    }
617
618    #[derive(Clone, Copy, Debug, PartialEq, Eq)]
619    struct AddInt(i32);
620
621    impl Semigroup for AddInt {
622        fn combine(&self, other: &Self) -> Self {
623            AddInt(self.0 + other.0)
624        }
625    }
626
627    impl Monoid for AddInt {
628        fn empty() -> Self {
629            AddInt(0)
630        }
631    }
632
633    impl CommutativeMonoid for AddInt {}
634
635    impl Group for AddInt {
636        fn inverse(&self) -> Self {
637            AddInt(-self.0)
638        }
639    }
640
641    impl AbelianGroup for AddInt {}
642
643    #[test]
644    fn group_has_inverse() {
645        let x = AddInt(5);
646        assert_eq!(x.combine(&x.inverse()), AddInt::empty());
647        assert_eq!(x.inverse().combine(&x), AddInt::empty());
648    }
649
650    #[test]
651    fn abelian_group_is_commutative() {
652        let x = AddInt(3);
653        let y = AddInt(-7);
654        assert_eq!(x.combine(&y), y.combine(&x));
655    }
656
657    #[test]
658    fn join_semilattice_leq() {
659        let a = Max(3);
660        let b = Max(5);
661        assert!(a.leq(&b));
662        assert!(!b.leq(&a));
663        assert!(a.leq(&a));
664    }
665
666    #[test]
667    fn join_all_works() {
668        let values = vec![Max(1), Max(5), Max(3)];
669        assert_eq!(Max::join_all(values), Some(Max(5)));
670    }
671
672    #[test]
673    fn join_all_empty_is_none() {
674        let empty: Vec<Max<i32>> = vec![];
675        assert_eq!(Max::join_all(empty), None);
676    }
677
678    #[test]
679    fn join_all_from_bottom_works() {
680        let values = vec![Max(1), Max(5), Max(3)];
681        assert_eq!(Max::join_all_from_bottom(values), Max(5));
682    }
683
684    #[test]
685    fn join_all_from_bottom_empty_is_bottom() {
686        let empty: Vec<Max<i32>> = vec![];
687        assert_eq!(Max::join_all_from_bottom(empty), Max::bottom());
688    }
689}