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}