algebra_core/lib.rs
1#![deny(missing_docs)]
2//! # algebra-core — Core algebraic abstractions
3//!
4//! **Part of the [postbox workspace](../index.html)**
5//!
6//! This crate provides fundamental algebraic structures as Rust
7//! traits:
8//!
9//! - [`Semigroup`]: associative binary operation
10//! - [`Monoid`]: semigroup with identity element
11//! - [`CommutativeMonoid`]: monoid with commutative operation
12//! - [`Group`]: monoid with inverse elements
13//! - [`AbelianGroup`]: commutative group
14//! - [`SemigroupHom`]: structure-preserving map between semigroups
15//! - [`MonoidHom`]: structure-preserving map between monoids
16//! - [`JoinSemilattice`]: associative, commutative, idempotent operation (least upper bound)
17//! - [`BoundedJoinSemilattice`]: join-semilattice with bottom element
18//! - [`MeetSemilattice`]: associative, commutative, idempotent operation (greatest lower bound)
19//! - [`BoundedMeetSemilattice`]: meet-semilattice with top element
20//! - [`Lattice`]: both join and meet operations
21//! - [`BoundedLattice`]: lattice with both bottom and top elements
22//!
23//! ### Recursion schemes
24//!
25//! - [`TypeApp`]: higher-kinded type encoding
26//! - [`Functor`]: functor on one type parameter
27//! - [`Fix`]: least fixed point (μF) for recursive data structures
28//! - [`fold`]: fold / catamorphism for `Fix` (given an F-algebra)
29//!
30//! ## Quick start
31//!
32//! ```rust
33//! use algebra_core::{Semigroup, Monoid, CommutativeMonoid, Sum};
34//!
35//! // Sum<T> is provided by the library for addition monoids
36//! let a = Sum(5);
37//! let b = Sum(3);
38//! let c = a.combine(&b);
39//! assert_eq!(c, Sum(8));
40//! assert_eq!(Sum::<i32>::empty(), Sum(0));
41//! ```
42//!
43//! ## Standard library implementations
44//!
45//! This crate provides implementations for common standard library types:
46//!
47//! ### Sets (union as join, intersection as meet)
48//!
49//! - **[`HashSet<T>`](std::collections::HashSet)**: `JoinSemilattice`,
50//! `BoundedJoinSemilattice`, `MeetSemilattice`, `Lattice`,
51//! `Semigroup`, `Monoid`, `CommutativeMonoid`
52//! - join = union, meet = intersection, bottom = ∅ (no top for arbitrary T)
53//! - **[`BTreeSet<T>`](std::collections::BTreeSet)**: `JoinSemilattice`,
54//! `BoundedJoinSemilattice`, `MeetSemilattice`, `Lattice`,
55//! `Semigroup`, `Monoid`, `CommutativeMonoid`
56//!
57//! ### Booleans (OR/AND lattice)
58//!
59//! - **[`bool`]**: `JoinSemilattice`, `BoundedJoinSemilattice`,
60//! `MeetSemilattice`, `BoundedMeetSemilattice`, `BoundedLattice`
61//! - join = OR, meet = AND, bottom = false, top = true
62//!
63//! ### Strings (concatenation)
64//!
65//! - **[`String`](String)**: `Semigroup`, `Monoid`
66//!
67//! ### Optional values (lifted monoid/lattice)
68//!
69//! - **[`Option<M>`](Option)** (where `M: Semigroup + Clone`): `Semigroup`
70//! - **[`Option<M>`](Option)** (where `M: Monoid + Clone`): `Monoid`, `Semigroup`
71//! - **[`Option<M>`](Option)** (where `M: CommutativeMonoid + Clone`): `CommutativeMonoid`
72//! - **[`Option<L>`](Option)** (where `L: JoinSemilattice + Clone`):
73//! `JoinSemilattice`, `BoundedJoinSemilattice`
74//! - `None` is bottom, `Some(a) ⊔ Some(b) = Some(a ⊔ b)`
75//! - **[`Option<L>`](Option)** (where `L: MeetSemilattice + Clone`):
76//! `MeetSemilattice`
77//! - `None ⊓ x = None` (bottom ⊓ anything = bottom)
78//! - **[`Option<L>`](Option)** (where `L: BoundedMeetSemilattice + Clone`):
79//! `BoundedMeetSemilattice`
80//! - top = `Some(L::top())`
81//!
82//! ### Tuples (product algebras)
83//!
84//! - **`()`**: All traits (trivial implementations)
85//! - **`(A,)`, `(A, B)`, `(A, B, C)`, `(A, B, C, D)`**:
86//! - `Semigroup`, `Monoid`, `CommutativeMonoid` (componentwise)
87//! - `JoinSemilattice`, `BoundedJoinSemilattice` (componentwise)
88//! - `MeetSemilattice`, `BoundedMeetSemilattice` (componentwise)
89//!
90//! All tuple implementations require component types to implement the
91//! corresponding trait, and operations are applied componentwise.
92
93// Make the current crate visible as `algebra_core` for consistency
94extern crate self as algebra_core;
95
96pub mod fix;
97pub use fix::{fold, Fix, Functor, TypeApp};
98
99/// A **semigroup**: a type with an associative binary operation.
100///
101/// Laws (not enforced by type system):
102///
103/// - **Associative**:
104/// `a.combine(b).combine(c) == a.combine(b.combine(c))`
105///
106/// # Example
107///
108/// ```rust
109/// use algebra_core::Semigroup;
110///
111/// #[derive(Clone, Copy, Debug, PartialEq, Eq)]
112/// struct Max(i32);
113///
114/// impl Semigroup for Max {
115/// fn combine(&self, other: &Self) -> Self {
116/// Max(self.0.max(other.0))
117/// }
118/// }
119///
120/// let x = Max(3);
121/// let y = Max(5);
122/// let z = Max(2);
123/// assert_eq!(x.combine(&y).combine(&z), x.combine(&y.combine(&z)));
124/// ```
125pub trait Semigroup: Sized {
126 /// Combine two elements associatively.
127 fn combine(&self, other: &Self) -> Self;
128
129 /// In-place combine.
130 fn combine_assign(&mut self, other: &Self) {
131 *self = self.combine(other);
132 }
133}
134
135/// A **monoid**: a semigroup with an identity element.
136///
137/// Laws (not enforced by type system):
138///
139/// - **Associative**:
140/// `a.combine(b).combine(c) == a.combine(b.combine(c))`
141/// - **Left identity**: `empty().combine(a) == a`
142/// - **Right identity**: `a.combine(empty()) == a`
143///
144/// # Example
145///
146/// ```rust
147/// use algebra_core::{Semigroup, Monoid};
148///
149/// #[derive(Clone, Copy, Debug, PartialEq, Eq)]
150/// struct Product(i32);
151///
152/// impl Semigroup for Product {
153/// fn combine(&self, other: &Self) -> Self {
154/// Product(self.0 * other.0)
155/// }
156/// }
157///
158/// impl Monoid for Product {
159/// fn empty() -> Self {
160/// Product(1)
161/// }
162/// }
163///
164/// let x = Product(3);
165/// let y = Product(5);
166/// assert_eq!(x.combine(&y), Product(15));
167/// assert_eq!(Product::empty().combine(&x), x);
168/// assert_eq!(x.combine(&Product::empty()), x);
169/// ```
170pub trait Monoid: Semigroup {
171 /// The identity element.
172 fn empty() -> Self;
173
174 /// Fold an iterator using combine, starting from empty.
175 fn concat<I>(iter: I) -> Self
176 where
177 I: IntoIterator<Item = Self>,
178 {
179 iter.into_iter()
180 .fold(Self::empty(), |acc, x| acc.combine(&x))
181 }
182}
183
184/// A **commutative monoid**: a monoid where combine is commutative.
185///
186/// Laws (not enforced by type system):
187///
188/// - **Associative**:
189/// `a.combine(b).combine(c) == a.combine(b.combine(c))`
190/// - **Commutative**: `a.combine(b) == b.combine(a)`
191/// - **Identity**: `a.combine(empty()) == a == empty().combine(a)`
192///
193/// # Example
194///
195/// ```rust
196/// use algebra_core::{Semigroup, Monoid, CommutativeMonoid};
197///
198/// #[derive(Clone, Copy, Debug, PartialEq, Eq)]
199/// struct Sum(i32);
200///
201/// impl Semigroup for Sum {
202/// fn combine(&self, other: &Self) -> Self {
203/// Sum(self.0 + other.0)
204/// }
205/// }
206///
207/// impl Monoid for Sum {
208/// fn empty() -> Self {
209/// Sum(0)
210/// }
211/// }
212///
213/// impl CommutativeMonoid for Sum {}
214///
215/// let x = Sum(3);
216/// let y = Sum(5);
217/// assert_eq!(x.combine(&y), y.combine(&x)); // commutative
218/// ```
219pub trait CommutativeMonoid: Monoid {
220 // Marker trait - laws are documented above
221}
222
223/// A **group**: a monoid where every element has an inverse.
224///
225/// Laws (not enforced by type system):
226///
227/// - **Associative**: `a.combine(b).combine(c) == a.combine(b.combine(c))`
228/// - **Identity**: `a.combine(empty()) == a == empty().combine(a)`
229/// - **Inverse**: `a.combine(a.inverse()) == empty() == a.inverse().combine(a)`
230///
231/// # Example
232///
233/// ```rust
234/// use algebra_core::{Semigroup, Monoid, Group};
235///
236/// #[derive(Clone, Copy, Debug, PartialEq, Eq)]
237/// struct AddInt(i32);
238///
239/// impl Semigroup for AddInt {
240/// fn combine(&self, other: &Self) -> Self {
241/// AddInt(self.0 + other.0)
242/// }
243/// }
244///
245/// impl Monoid for AddInt {
246/// fn empty() -> Self {
247/// AddInt(0)
248/// }
249/// }
250///
251/// impl Group for AddInt {
252/// fn inverse(&self) -> Self {
253/// AddInt(-self.0)
254/// }
255/// }
256///
257/// let x = AddInt(5);
258/// let inv = x.inverse();
259/// assert_eq!(x.combine(&inv), AddInt::empty());
260/// ```
261pub trait Group: Monoid {
262 /// Return the inverse of this element.
263 fn inverse(&self) -> Self;
264}
265
266/// An **abelian group** (commutative group): a group where combine is
267/// commutative.
268///
269/// Laws (not enforced by type system):
270///
271/// - **Associative**: `a.combine(b).combine(c) == a.combine(b.combine(c))`
272/// - **Commutative**: `a.combine(b) == b.combine(a)`
273/// - **Identity**: `a.combine(empty()) == a == empty().combine(a)`
274/// - **Inverse**: `a.combine(a.inverse()) == empty() == a.inverse().combine(a)`
275///
276/// Named after mathematician Niels Henrik Abel.
277pub trait AbelianGroup: Group + CommutativeMonoid {
278 // Marker trait - combines Group and CommutativeMonoid
279}
280
281/// A **semigroup homomorphism**: a structure-preserving map between
282/// semigroups.
283///
284/// A homomorphism `f: S → T` preserves the semigroup operation:
285///
286/// Laws (not enforced by type system):
287///
288/// - **Preserve combine**: `f(x.combine(y)) == f(x).combine(f(y))`
289///
290/// # Example
291///
292/// ```rust
293/// use algebra_core::{Semigroup, Monoid, SemigroupHom};
294///
295/// // Wrapper for usize with addition
296/// #[derive(Debug, Clone, Copy, PartialEq, Eq)]
297/// struct Sum(usize);
298///
299/// impl Semigroup for Sum {
300/// fn combine(&self, other: &Self) -> Self {
301/// Sum(self.0 + other.0)
302/// }
303/// }
304///
305/// impl Monoid for Sum {
306/// fn empty() -> Self { Sum(0) }
307/// }
308///
309/// // String length is a homomorphism from (String, concat) to (Sum, +)
310/// struct Length;
311///
312/// impl SemigroupHom for Length {
313/// type Source = String;
314/// type Target = Sum;
315///
316/// fn apply(&self, s: &String) -> Sum {
317/// Sum(s.len())
318/// }
319/// }
320///
321/// // Verify: len(s1 + s2) = len(s1) + len(s2)
322/// let len = Length;
323/// let s1 = String::from("hello");
324/// let s2 = String::from("world");
325/// assert_eq!(
326/// len.apply(&s1.clone().combine(&s2)),
327/// len.apply(&s1).combine(&len.apply(&s2))
328/// );
329/// ```
330pub trait SemigroupHom {
331 /// The source semigroup
332 type Source: Semigroup;
333
334 /// The target semigroup
335 type Target: Semigroup;
336
337 /// Apply the homomorphism
338 fn apply(&self, x: &Self::Source) -> Self::Target;
339}
340
341/// Helper trait for explicitly specifying source and target types.
342///
343/// This is a blanket-implemented alias that allows writing
344/// `T: SemigroupHomFromTo<S, T>` instead of
345/// `T: SemigroupHom<Source = S, Target = T>`.
346pub trait SemigroupHomFromTo<S: Semigroup, T: Semigroup>:
347 SemigroupHom<Source = S, Target = T>
348{
349}
350
351impl<H, S, T> SemigroupHomFromTo<S, T> for H
352where
353 H: SemigroupHom<Source = S, Target = T>,
354 S: Semigroup,
355 T: Semigroup,
356{
357}
358
359/// A **monoid homomorphism**: a structure-preserving map between
360/// monoids.
361///
362/// A homomorphism `f: M → N` preserves both the monoid operation and
363/// identity:
364///
365/// Laws (not enforced by type system):
366///
367/// - **Preserve combine**: `f(x.combine(y)) == f(x).combine(f(y))`
368/// - **Preserve identity**: `f(M::empty()) == N::empty()`
369///
370/// # Example
371///
372/// ```rust
373/// use algebra_core::{Monoid, Semigroup, MonoidHom, SemigroupHom};
374/// use std::collections::HashSet;
375///
376/// // Wrapper for usize with addition
377/// #[derive(Debug, Clone, Copy, PartialEq, Eq)]
378/// struct Sum(usize);
379///
380/// impl Semigroup for Sum {
381/// fn combine(&self, other: &Self) -> Self {
382/// Sum(self.0 + other.0)
383/// }
384/// }
385///
386/// impl Monoid for Sum {
387/// fn empty() -> Self { Sum(0) }
388/// }
389///
390/// // Set cardinality: a monoid homomorphism from (HashSet, ∪) to
391/// // (Sum, +)
392/// // Note: Exact homomorphism property holds for disjoint unions
393/// struct Cardinality;
394///
395/// impl SemigroupHom for Cardinality {
396/// type Source = HashSet<i32>;
397/// type Target = Sum;
398///
399/// fn apply(&self, s: &HashSet<i32>) -> Sum {
400/// Sum(s.len())
401/// }
402/// }
403///
404/// impl MonoidHom for Cardinality {}
405///
406/// // Verify identity preservation: |∅| = 0
407/// let card = Cardinality;
408/// assert_eq!(card.apply(&HashSet::empty()), Sum::empty());
409/// ```
410pub trait MonoidHom: SemigroupHom {
411 // Source and Target are already constrained to be Semigroups. We
412 // further require them to be Monoids (but Rust doesn't let us
413 // re-constrain associated types, so this is documented in the
414 // laws)
415}
416
417/// Helper trait for explicitly specifying source and target monoids.
418///
419/// This is a blanket-implemented alias that allows writing
420/// `T: MonoidHomFromTo<M, N>` instead of
421/// `T: MonoidHom<Source = M, Target = N>`.
422pub trait MonoidHomFromTo<M: Monoid, N: Monoid>: MonoidHom<Source = M, Target = N> {}
423
424impl<H, M, N> MonoidHomFromTo<M, N> for H
425where
426 H: MonoidHom<Source = M, Target = N>,
427 M: Monoid,
428 N: Monoid,
429{
430}
431
432/// A **join-semilattice**: a type with an associative, commutative,
433/// idempotent binary operation.
434///
435/// Laws (not enforced by type system):
436///
437/// - **Associative**: `a.join(b).join(c) == a.join(b.join(c))`
438/// - **Commutative**: `a.join(b) == b.join(a)`
439/// - **Idempotent**: `a.join(a) == a`
440///
441/// The `join` operation computes the least upper bound (supremum) in the
442/// induced partial order: `x ≤ y` iff `x.join(y) == y`.
443///
444/// # Example
445///
446/// ```rust
447/// use algebra_core::JoinSemilattice;
448/// use std::collections::HashSet;
449///
450/// let a: HashSet<_> = [1, 2].into_iter().collect();
451/// let b: HashSet<_> = [2, 3].into_iter().collect();
452///
453/// // join = union
454/// let c = a.join(&b);
455/// assert_eq!(c, [1, 2, 3].into_iter().collect());
456///
457/// // Idempotent
458/// assert_eq!(a.join(&a), a);
459/// ```
460pub trait JoinSemilattice: Sized {
461 /// The join (least upper bound).
462 fn join(&self, other: &Self) -> Self;
463
464 /// In-place variant.
465 fn join_assign(&mut self, other: &Self) {
466 *self = self.join(other);
467 }
468
469 /// Derived partial order: x ≤ y iff x ∨ y = y.
470 fn leq(&self, other: &Self) -> bool
471 where
472 Self: PartialEq,
473 {
474 self.join(other) == *other
475 }
476
477 /// Join a finite iterator of values. Returns `None` for empty iterators.
478 fn join_all<I>(it: I) -> Option<Self>
479 where
480 I: IntoIterator<Item = Self>,
481 {
482 it.into_iter().reduce(|acc, x| acc.join(&x))
483 }
484}
485
486/// A **bounded join-semilattice**: a join-semilattice with a bottom element.
487///
488/// Laws (not enforced by type system):
489///
490/// - **Associative**: `a.join(b).join(c) == a.join(b.join(c))`
491/// - **Commutative**: `a.join(b) == b.join(a)`
492/// - **Idempotent**: `a.join(a) == a`
493/// - **Identity**: `bottom().join(a) == a == a.join(bottom())`
494///
495/// The bottom element (⊥) is the least element in the partial order.
496///
497/// # Example
498///
499/// ```rust
500/// use algebra_core::{BoundedJoinSemilattice, JoinSemilattice};
501/// use std::collections::HashSet;
502///
503/// let a: HashSet<_> = [1, 2].into_iter().collect();
504///
505/// // bottom = empty set
506/// let bottom = HashSet::<i32>::bottom();
507/// assert!(bottom.is_empty());
508///
509/// // Identity law
510/// assert_eq!(bottom.join(&a), a);
511/// assert_eq!(a.join(&bottom), a);
512/// ```
513pub trait BoundedJoinSemilattice: JoinSemilattice {
514 /// The bottom element of the lattice (⊥).
515 ///
516 /// This is the least element w.r.t. the induced partial order:
517 /// for all `x`, `bottom().join(x) == x`.
518 fn bottom() -> Self;
519
520 /// Join a finite iterator of values, starting from ⊥.
521 ///
522 /// Never returns `None`: an empty iterator produces `bottom()`.
523 fn join_all_from_bottom<I>(it: I) -> Self
524 where
525 I: IntoIterator<Item = Self>,
526 {
527 it.into_iter().fold(Self::bottom(), |acc, x| acc.join(&x))
528 }
529}
530
531/// A **meet-semilattice**: a type with an associative, commutative,
532/// idempotent binary operation that computes greatest lower bounds.
533///
534/// Laws (not enforced by type system):
535///
536/// - **Associative**: `a.meet(b).meet(c) == a.meet(b.meet(c))`
537/// - **Commutative**: `a.meet(b) == b.meet(a)`
538/// - **Idempotent**: `a.meet(a) == a`
539///
540/// The `meet` operation computes the greatest lower bound (infimum) in the
541/// induced partial order: `x ≤ y` iff `x.meet(y) == x`.
542///
543/// # Example
544///
545/// ```rust
546/// use algebra_core::MeetSemilattice;
547/// use std::collections::HashSet;
548///
549/// let a: HashSet<_> = [1, 2, 3].into_iter().collect();
550/// let b: HashSet<_> = [2, 3, 4].into_iter().collect();
551///
552/// // meet = intersection
553/// let c = a.meet(&b);
554/// assert_eq!(c, [2, 3].into_iter().collect());
555///
556/// // Idempotent
557/// assert_eq!(a.meet(&a), a);
558/// ```
559pub trait MeetSemilattice: Sized {
560 /// The meet (greatest lower bound).
561 fn meet(&self, other: &Self) -> Self;
562
563 /// In-place variant.
564 fn meet_assign(&mut self, other: &Self) {
565 *self = self.meet(other);
566 }
567
568 /// Derived partial order: x ≤ y iff x ∧ y = x.
569 fn leq_meet(&self, other: &Self) -> bool
570 where
571 Self: PartialEq,
572 {
573 self.meet(other) == *self
574 }
575
576 /// Meet a finite iterator of values. Returns `None` for empty iterators.
577 fn meet_all<I>(it: I) -> Option<Self>
578 where
579 I: IntoIterator<Item = Self>,
580 {
581 it.into_iter().reduce(|acc, x| acc.meet(&x))
582 }
583}
584
585/// A **bounded meet-semilattice**: a meet-semilattice with a top element.
586///
587/// Laws (not enforced by type system):
588///
589/// - **Associative**: `a.meet(b).meet(c) == a.meet(b.meet(c))`
590/// - **Commutative**: `a.meet(b) == b.meet(a)`
591/// - **Idempotent**: `a.meet(a) == a`
592/// - **Identity**: `top().meet(a) == a == a.meet(top())`
593///
594/// The top element (⊤) is the greatest element in the partial order.
595///
596/// # Example
597///
598/// ```rust
599/// use algebra_core::{BoundedMeetSemilattice, MeetSemilattice};
600///
601/// // For bool: top = true, meet = AND
602/// let a = true;
603/// let top = bool::top();
604/// assert_eq!(top, true);
605/// assert_eq!(top.meet(&a), a);
606/// assert_eq!(a.meet(&top), a);
607/// ```
608pub trait BoundedMeetSemilattice: MeetSemilattice {
609 /// The top element of the lattice (⊤).
610 ///
611 /// This is the greatest element w.r.t. the induced partial order:
612 /// for all `x`, `top().meet(x) == x`.
613 fn top() -> Self;
614
615 /// Meet a finite iterator of values, starting from ⊤.
616 ///
617 /// Never returns `None`: an empty iterator produces `top()`.
618 fn meet_all_from_top<I>(it: I) -> Self
619 where
620 I: IntoIterator<Item = Self>,
621 {
622 it.into_iter().fold(Self::top(), |acc, x| acc.meet(&x))
623 }
624}
625
626/// A **lattice**: a type with both join (least upper bound) and meet
627/// (greatest lower bound) operations.
628///
629/// Laws (not enforced by type system):
630///
631/// - All join-semilattice laws for `join`
632/// - All meet-semilattice laws for `meet`
633/// - **Absorption**: `a.join(a.meet(b)) == a` and `a.meet(a.join(b)) == a`
634///
635/// # Example
636///
637/// ```rust
638/// use algebra_core::{JoinSemilattice, MeetSemilattice, Lattice};
639/// use std::collections::HashSet;
640///
641/// let a: HashSet<_> = [1, 2].into_iter().collect();
642/// let b: HashSet<_> = [2, 3].into_iter().collect();
643///
644/// // join = union, meet = intersection
645/// assert_eq!(a.join(&b), [1, 2, 3].into_iter().collect());
646/// assert_eq!(a.meet(&b), [2].into_iter().collect());
647/// ```
648pub trait Lattice: JoinSemilattice + MeetSemilattice {}
649
650impl<T: JoinSemilattice + MeetSemilattice> Lattice for T {}
651
652/// A **bounded lattice**: a lattice with both bottom and top elements.
653///
654/// Laws (not enforced by type system):
655///
656/// - All bounded join-semilattice laws
657/// - All bounded meet-semilattice laws
658/// - **Absorption**: `a.join(a.meet(b)) == a` and `a.meet(a.join(b)) == a`
659/// - **Complement bounds**: `bottom().meet(a) == bottom()` and `top().join(a) == top()`
660///
661/// # Example
662///
663/// ```rust
664/// use algebra_core::{BoundedJoinSemilattice, BoundedMeetSemilattice, BoundedLattice};
665///
666/// // bool is a bounded lattice
667/// assert_eq!(bool::bottom(), false);
668/// assert_eq!(bool::top(), true);
669/// ```
670pub trait BoundedLattice: BoundedJoinSemilattice + BoundedMeetSemilattice {}
671
672impl<T: BoundedJoinSemilattice + BoundedMeetSemilattice> BoundedLattice for T {}
673
674// Implementations for standard library types
675
676use std::collections::{BTreeSet, HashSet};
677use std::hash::Hash;
678
679// HashSet: join = union
680
681impl<T: Eq + Hash + Clone> JoinSemilattice for HashSet<T> {
682 fn join(&self, other: &Self) -> Self {
683 self.union(other).cloned().collect()
684 }
685}
686
687impl<T: Eq + Hash + Clone> BoundedJoinSemilattice for HashSet<T> {
688 fn bottom() -> Self {
689 HashSet::new()
690 }
691}
692
693impl<T: Eq + Hash + Clone> Semigroup for HashSet<T> {
694 fn combine(&self, other: &Self) -> Self {
695 self.join(other)
696 }
697}
698
699impl<T: Eq + Hash + Clone> Monoid for HashSet<T> {
700 fn empty() -> Self {
701 Self::bottom()
702 }
703}
704
705impl<T: Eq + Hash + Clone> CommutativeMonoid for HashSet<T> {}
706
707impl<T: Eq + Hash + Clone> MeetSemilattice for HashSet<T> {
708 fn meet(&self, other: &Self) -> Self {
709 self.intersection(other).cloned().collect()
710 }
711}
712
713// Note: HashSet does NOT implement BoundedMeetSemilattice because
714// the universal set (top) cannot be represented for arbitrary T.
715
716// BTreeSet: join = union
717
718impl<T: Ord + Clone> JoinSemilattice for BTreeSet<T> {
719 fn join(&self, other: &Self) -> Self {
720 self.union(other).cloned().collect()
721 }
722}
723
724impl<T: Ord + Clone> BoundedJoinSemilattice for BTreeSet<T> {
725 fn bottom() -> Self {
726 BTreeSet::new()
727 }
728}
729
730impl<T: Ord + Clone> Semigroup for BTreeSet<T> {
731 fn combine(&self, other: &Self) -> Self {
732 self.join(other)
733 }
734}
735
736impl<T: Ord + Clone> Monoid for BTreeSet<T> {
737 fn empty() -> Self {
738 Self::bottom()
739 }
740}
741
742impl<T: Ord + Clone> CommutativeMonoid for BTreeSet<T> {}
743
744impl<T: Ord + Clone> MeetSemilattice for BTreeSet<T> {
745 fn meet(&self, other: &Self) -> Self {
746 self.intersection(other).cloned().collect()
747 }
748}
749
750// Note: BTreeSet does NOT implement BoundedMeetSemilattice because
751// the universal set (top) cannot be represented for arbitrary T.
752
753// bool: join = OR, meet = AND
754
755impl JoinSemilattice for bool {
756 fn join(&self, other: &Self) -> Self {
757 *self || *other
758 }
759}
760
761impl BoundedJoinSemilattice for bool {
762 fn bottom() -> Self {
763 false
764 }
765}
766
767impl MeetSemilattice for bool {
768 fn meet(&self, other: &Self) -> Self {
769 *self && *other
770 }
771}
772
773impl BoundedMeetSemilattice for bool {
774 fn top() -> Self {
775 true
776 }
777}
778
779// String: combine = concatenation
780
781impl Semigroup for String {
782 fn combine(&self, other: &Self) -> Self {
783 let mut result = self.clone();
784 result.push_str(other);
785 result
786 }
787}
788
789impl Monoid for String {
790 fn empty() -> Self {
791 String::new()
792 }
793}
794
795// ============================================================
796// Numeric wrappers: Sum and Product
797// ============================================================
798
799/// Wrapper type for the addition monoid.
800///
801/// `Sum<T>` forms a monoid under addition (`+`), making it useful for
802/// accumulating numeric values, counting, or gradient accumulation in
803/// automatic differentiation.
804///
805/// # Examples
806///
807/// ```
808/// use algebra_core::{Monoid, Semigroup, Sum};
809///
810/// let a = Sum(5);
811/// let b = Sum(3);
812/// let c = a.combine(&b);
813/// assert_eq!(c, Sum(8));
814///
815/// assert_eq!(Sum::<i32>::empty(), Sum(0));
816/// ```
817///
818/// # Use in Autodiff
819///
820/// Gradient accumulation in reverse-mode automatic differentiation
821/// is fundamentally `Sum<f64>`:
822///
823/// ```
824/// use algebra_core::{Semigroup, Sum};
825///
826/// let grad1 = Sum(0.5);
827/// let grad2 = Sum(0.3);
828/// let total_grad = grad1.combine(&grad2);
829/// assert_eq!(total_grad.0, 0.8);
830/// ```
831#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
832pub struct Sum<T>(pub T);
833
834impl<T: std::ops::Add<Output = T> + Clone> Semigroup for Sum<T> {
835 fn combine(&self, other: &Self) -> Self {
836 Sum(self.0.clone() + other.0.clone())
837 }
838}
839
840impl<T: std::ops::Add<Output = T> + Clone + num_traits::Zero> Monoid for Sum<T> {
841 fn empty() -> Self {
842 Sum(T::zero())
843 }
844}
845
846impl<T: std::ops::Add<Output = T> + Clone + num_traits::Zero> CommutativeMonoid for Sum<T> {}
847
848/// Wrapper type for the multiplication monoid.
849///
850/// `Product<T>` forms a monoid under multiplication (`*`), useful for
851/// computing products, scaling factors, or combining probabilities.
852///
853/// # Examples
854///
855/// ```
856/// use algebra_core::{Monoid, Semigroup, Product};
857///
858/// let a = Product(5);
859/// let b = Product(3);
860/// let c = a.combine(&b);
861/// assert_eq!(c, Product(15));
862///
863/// assert_eq!(Product::<i32>::empty(), Product(1));
864/// ```
865///
866/// # Combining Probabilities
867///
868/// ```
869/// use algebra_core::{Semigroup, Product};
870///
871/// let prob1 = Product(0.5);
872/// let prob2 = Product(0.5);
873/// let joint_prob = prob1.combine(&prob2);
874/// assert_eq!(joint_prob.0, 0.25);
875/// ```
876#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
877pub struct Product<T>(pub T);
878
879impl<T: std::ops::Mul<Output = T> + Clone> Semigroup for Product<T> {
880 fn combine(&self, other: &Self) -> Self {
881 Product(self.0.clone() * other.0.clone())
882 }
883}
884
885impl<T: std::ops::Mul<Output = T> + Clone + num_traits::One> Monoid for Product<T> {
886 fn empty() -> Self {
887 Product(T::one())
888 }
889}
890
891impl<T: std::ops::Mul<Output = T> + Clone + num_traits::One> CommutativeMonoid for Product<T> {}
892
893// Option: lifted lattice
894
895impl<L: JoinSemilattice + Clone> JoinSemilattice for Option<L> {
896 fn join(&self, other: &Self) -> Self {
897 match (self, other) {
898 (None, x) | (x, None) => x.clone(),
899 (Some(a), Some(b)) => Some(a.join(b)),
900 }
901 }
902}
903
904impl<L: JoinSemilattice + Clone> BoundedJoinSemilattice for Option<L> {
905 fn bottom() -> Self {
906 None
907 }
908}
909
910impl<M: Semigroup + Clone> Semigroup for Option<M> {
911 fn combine(&self, other: &Self) -> Self {
912 match (self, other) {
913 (None, x) | (x, None) => x.clone(),
914 (Some(a), Some(b)) => Some(a.combine(b)),
915 }
916 }
917}
918
919impl<M: Monoid + Clone> Monoid for Option<M> {
920 fn empty() -> Self {
921 None
922 }
923}
924
925impl<M: CommutativeMonoid + Clone> CommutativeMonoid for Option<M> {}
926
927impl<L: MeetSemilattice + Clone> MeetSemilattice for Option<L> {
928 fn meet(&self, other: &Self) -> Self {
929 match (self, other) {
930 (Some(a), Some(b)) => Some(a.meet(b)),
931 // None is bottom, and bottom ⊓ x = bottom
932 _ => None,
933 }
934 }
935}
936
937impl<L: BoundedMeetSemilattice + Clone> BoundedMeetSemilattice for Option<L> {
938 fn top() -> Self {
939 Some(L::top())
940 }
941}
942
943// Unit type
944
945impl JoinSemilattice for () {
946 fn join(&self, _other: &Self) -> Self {}
947}
948
949impl BoundedJoinSemilattice for () {
950 fn bottom() -> Self {}
951}
952
953impl Semigroup for () {
954 fn combine(&self, _other: &Self) -> Self {}
955}
956
957impl Monoid for () {
958 fn empty() -> Self {}
959}
960
961impl CommutativeMonoid for () {}
962
963impl Group for () {
964 fn inverse(&self) -> Self {}
965}
966
967impl AbelianGroup for () {}
968
969impl MeetSemilattice for () {
970 fn meet(&self, _other: &Self) -> Self {}
971}
972
973impl BoundedMeetSemilattice for () {
974 fn top() -> Self {}
975}
976
977// Tuples: product lattices
978
979macro_rules! impl_product_lattice {
980 ( $( $T:ident : $idx:tt ),+ ) => {
981 impl<$( $T ),+> JoinSemilattice for ( $( $T, )+ )
982 where
983 $( $T: JoinSemilattice ),+
984 {
985 fn join(&self, other: &Self) -> Self {
986 (
987 $( self.$idx.join(&other.$idx), )+
988 )
989 }
990 }
991
992 impl<$( $T ),+> BoundedJoinSemilattice for ( $( $T, )+ )
993 where
994 $( $T: BoundedJoinSemilattice ),+
995 {
996 fn bottom() -> Self {
997 (
998 $( $T::bottom(), )+
999 )
1000 }
1001 }
1002
1003 impl<$( $T ),+> MeetSemilattice for ( $( $T, )+ )
1004 where
1005 $( $T: MeetSemilattice ),+
1006 {
1007 fn meet(&self, other: &Self) -> Self {
1008 (
1009 $( self.$idx.meet(&other.$idx), )+
1010 )
1011 }
1012 }
1013
1014 impl<$( $T ),+> BoundedMeetSemilattice for ( $( $T, )+ )
1015 where
1016 $( $T: BoundedMeetSemilattice ),+
1017 {
1018 fn top() -> Self {
1019 (
1020 $( $T::top(), )+
1021 )
1022 }
1023 }
1024 }
1025}
1026
1027impl_product_lattice!(A:0);
1028impl_product_lattice!(A:0, B:1);
1029impl_product_lattice!(A:0, B:1, C:2);
1030impl_product_lattice!(A:0, B:1, C:2, D:3);
1031
1032// Tuples: product monoids
1033
1034macro_rules! impl_product_monoid {
1035 ( $( $T:ident : $idx:tt ),+ ) => {
1036 impl<$( $T ),+> Semigroup for ( $( $T, )+ )
1037 where
1038 $( $T: Semigroup ),+
1039 {
1040 fn combine(&self, other: &Self) -> Self {
1041 (
1042 $( self.$idx.combine(&other.$idx), )+
1043 )
1044 }
1045 }
1046
1047 impl<$( $T ),+> Monoid for ( $( $T, )+ )
1048 where
1049 $( $T: Monoid ),+
1050 {
1051 fn empty() -> Self {
1052 (
1053 $( $T::empty(), )+
1054 )
1055 }
1056 }
1057
1058 impl<$( $T ),+> CommutativeMonoid for ( $( $T, )+ )
1059 where
1060 $( $T: CommutativeMonoid ),+
1061 {
1062 }
1063 }
1064}
1065
1066impl_product_monoid!(A:0);
1067impl_product_monoid!(A:0, B:1);
1068impl_product_monoid!(A:0, B:1, C:2);
1069impl_product_monoid!(A:0, B:1, C:2, D:3);
1070
1071// Re-export derive macros when derive feature is enabled
1072#[cfg(feature = "derive")]
1073pub use algebra_core_derive::{
1074 AbelianGroup, BoundedJoinSemilattice, BoundedMeetSemilattice, CommutativeMonoid, Group,
1075 JoinSemilattice, MeetSemilattice, Monoid, Semigroup,
1076};
1077
1078#[cfg(test)]
1079mod tests {
1080 use super::*;
1081
1082 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
1083 struct Sum(i32);
1084
1085 impl Semigroup for Sum {
1086 fn combine(&self, other: &Self) -> Self {
1087 Sum(self.0 + other.0)
1088 }
1089 }
1090
1091 impl Monoid for Sum {
1092 fn empty() -> Self {
1093 Sum(0)
1094 }
1095 }
1096
1097 impl CommutativeMonoid for Sum {}
1098
1099 #[test]
1100 fn semigroup_combine_works() {
1101 let x = Sum(3);
1102 let y = Sum(5);
1103 assert_eq!(x.combine(&y), Sum(8));
1104 }
1105
1106 #[test]
1107 fn semigroup_is_associative() {
1108 let x = Sum(1);
1109 let y = Sum(2);
1110 let z = Sum(3);
1111 assert_eq!(x.combine(&y).combine(&z), x.combine(&y.combine(&z)));
1112 }
1113
1114 #[test]
1115 fn monoid_has_identity() {
1116 let x = Sum(5);
1117 assert_eq!(Sum::empty().combine(&x), x);
1118 assert_eq!(x.combine(&Sum::empty()), x);
1119 }
1120
1121 #[test]
1122 fn monoid_concat_works() {
1123 let values = vec![Sum(1), Sum(2), Sum(3)];
1124 assert_eq!(Sum::concat(values), Sum(6));
1125 }
1126
1127 #[test]
1128 fn monoid_concat_empty_is_identity() {
1129 let empty: Vec<Sum> = vec![];
1130 assert_eq!(Sum::concat(empty), Sum::empty());
1131 }
1132
1133 #[test]
1134 fn commutative_monoid_is_commutative() {
1135 let x = Sum(3);
1136 let y = Sum(5);
1137 assert_eq!(x.combine(&y), y.combine(&x));
1138 }
1139
1140 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
1141 struct AddInt(i32);
1142
1143 impl Semigroup for AddInt {
1144 fn combine(&self, other: &Self) -> Self {
1145 AddInt(self.0 + other.0)
1146 }
1147 }
1148
1149 impl Monoid for AddInt {
1150 fn empty() -> Self {
1151 AddInt(0)
1152 }
1153 }
1154
1155 impl CommutativeMonoid for AddInt {}
1156
1157 impl Group for AddInt {
1158 fn inverse(&self) -> Self {
1159 AddInt(-self.0)
1160 }
1161 }
1162
1163 impl AbelianGroup for AddInt {}
1164
1165 #[test]
1166 fn group_has_inverse() {
1167 let x = AddInt(5);
1168 assert_eq!(x.combine(&x.inverse()), AddInt::empty());
1169 assert_eq!(x.inverse().combine(&x), AddInt::empty());
1170 }
1171
1172 #[test]
1173 fn abelian_group_is_commutative() {
1174 let x = AddInt(3);
1175 let y = AddInt(-7);
1176 assert_eq!(x.combine(&y), y.combine(&x));
1177 }
1178
1179 // ============================================================
1180 // Standard library implementations
1181 // ============================================================
1182
1183 #[test]
1184 fn hashset_semigroup_is_union() {
1185 use std::collections::HashSet;
1186 let a: HashSet<_> = [1, 2, 3].into_iter().collect();
1187 let b: HashSet<_> = [3, 4, 5].into_iter().collect();
1188 let result = a.combine(&b);
1189 let expected: HashSet<_> = [1, 2, 3, 4, 5].into_iter().collect();
1190 assert_eq!(result, expected);
1191 }
1192
1193 #[test]
1194 fn hashset_monoid_empty_is_empty_set() {
1195 use std::collections::HashSet;
1196 let empty = HashSet::<i32>::empty();
1197 assert!(empty.is_empty());
1198 }
1199
1200 #[test]
1201 fn hashset_join_semilattice_is_union() {
1202 use std::collections::HashSet;
1203 let a: HashSet<_> = [1, 2].into_iter().collect();
1204 let b: HashSet<_> = [2, 3].into_iter().collect();
1205 assert_eq!(a.join(&b), [1, 2, 3].into_iter().collect());
1206 }
1207
1208 #[test]
1209 fn hashset_bottom_is_empty() {
1210 use std::collections::HashSet;
1211 let bottom = HashSet::<i32>::bottom();
1212 assert!(bottom.is_empty());
1213 }
1214
1215 #[test]
1216 fn btreeset_semigroup_is_union() {
1217 use std::collections::BTreeSet;
1218 let a: BTreeSet<_> = [1, 2, 3].into_iter().collect();
1219 let b: BTreeSet<_> = [3, 4, 5].into_iter().collect();
1220 let result = a.combine(&b);
1221 let expected: BTreeSet<_> = [1, 2, 3, 4, 5].into_iter().collect();
1222 assert_eq!(result, expected);
1223 }
1224
1225 #[test]
1226 fn btreeset_bottom_is_empty() {
1227 use std::collections::BTreeSet;
1228 let bottom = BTreeSet::<i32>::bottom();
1229 assert!(bottom.is_empty());
1230 }
1231
1232 #[test]
1233 fn string_semigroup_is_concatenation() {
1234 let a = String::from("Hello");
1235 let b = String::from(" World");
1236 assert_eq!(a.combine(&b), "Hello World");
1237 }
1238
1239 #[test]
1240 fn string_monoid_empty_is_empty_string() {
1241 assert_eq!(String::empty(), "");
1242 }
1243
1244 #[test]
1245 fn option_semigroup_combines_inner_values() {
1246 let a = Some(Sum(3));
1247 let b = Some(Sum(5));
1248 assert_eq!(a.combine(&b), Some(Sum(8)));
1249 }
1250
1251 #[test]
1252 fn option_semigroup_none_propagates() {
1253 let a: Option<Sum> = None;
1254 let b = Some(Sum(5));
1255 assert_eq!(a.combine(&b), Some(Sum(5)));
1256 assert_eq!(b.combine(&a), Some(Sum(5)));
1257 }
1258
1259 #[test]
1260 fn option_monoid_empty_is_none() {
1261 assert_eq!(Option::<Sum>::empty(), None);
1262 }
1263
1264 #[test]
1265 fn option_join_semilattice_joins_inner() {
1266 use std::collections::HashSet;
1267 let a: Option<HashSet<_>> = Some([1, 2].into_iter().collect());
1268 let b: Option<HashSet<_>> = Some([2, 3].into_iter().collect());
1269 let expected: Option<HashSet<_>> = Some([1, 2, 3].into_iter().collect());
1270 assert_eq!(a.join(&b), expected);
1271 }
1272
1273 #[test]
1274 fn option_bottom_is_none() {
1275 use std::collections::HashSet;
1276 let bottom = Option::<HashSet<i32>>::bottom();
1277 assert_eq!(bottom, None);
1278 }
1279
1280 // ============================================================
1281 // Sum<T> and Product<T> tests
1282 // ============================================================
1283
1284 #[test]
1285 fn sum_wrapper_implements_semigroup() {
1286 use crate::Sum;
1287 let a = Sum(5);
1288 let b = Sum(3);
1289 assert_eq!(a.combine(&b), Sum(8));
1290 }
1291
1292 #[test]
1293 fn sum_wrapper_monoid_empty_is_zero() {
1294 use crate::Sum;
1295 assert_eq!(Sum::<i32>::empty(), Sum(0));
1296 }
1297
1298 #[test]
1299 fn product_wrapper_implements_semigroup() {
1300 use crate::Product;
1301 let a = Product(5);
1302 let b = Product(3);
1303 assert_eq!(a.combine(&b), Product(15));
1304 }
1305
1306 #[test]
1307 fn product_wrapper_monoid_empty_is_one() {
1308 use crate::Product;
1309 assert_eq!(Product::<i32>::empty(), Product(1));
1310 }
1311
1312 // ============================================================
1313 // MeetSemilattice tests
1314 // ============================================================
1315
1316 #[test]
1317 fn hashset_meet_is_intersection() {
1318 use std::collections::HashSet;
1319 let a: HashSet<_> = [1, 2, 3].into_iter().collect();
1320 let b: HashSet<_> = [2, 3, 4].into_iter().collect();
1321 assert_eq!(a.meet(&b), [2, 3].into_iter().collect());
1322 }
1323
1324 #[test]
1325 fn hashset_meet_is_commutative() {
1326 use std::collections::HashSet;
1327 let a: HashSet<_> = [1, 2].into_iter().collect();
1328 let b: HashSet<_> = [2, 3].into_iter().collect();
1329 assert_eq!(a.meet(&b), b.meet(&a));
1330 }
1331
1332 #[test]
1333 fn hashset_is_lattice() {
1334 use std::collections::HashSet;
1335 let a: HashSet<_> = [1, 2].into_iter().collect();
1336 let b: HashSet<_> = [2, 3].into_iter().collect();
1337 // join = union, meet = intersection
1338 assert_eq!(a.join(&b), [1, 2, 3].into_iter().collect());
1339 assert_eq!(a.meet(&b), [2].into_iter().collect());
1340 }
1341
1342 #[test]
1343 fn bool_is_bounded_lattice() {
1344 // join = OR, meet = AND
1345 assert!(true.join(&false));
1346 assert!(!true.meet(&false));
1347 assert!(!bool::bottom());
1348 assert!(bool::top());
1349 }
1350
1351 #[test]
1352 fn bool_absorption_laws() {
1353 // a ∨ (a ∧ b) = a
1354 // a ∧ (a ∨ b) = a
1355 for a in [true, false] {
1356 for b in [true, false] {
1357 assert_eq!(a.join(&a.meet(&b)), a);
1358 assert_eq!(a.meet(&a.join(&b)), a);
1359 }
1360 }
1361 }
1362
1363 #[test]
1364 fn option_meet_with_none_is_none() {
1365 use std::collections::HashSet;
1366 let a: Option<HashSet<_>> = Some([1, 2].into_iter().collect());
1367 let b: Option<HashSet<i32>> = None;
1368 // None is bottom, and bottom ∧ x = bottom
1369 assert_eq!(a.meet(&b), None);
1370 assert_eq!(b.meet(&a), None);
1371 }
1372
1373 #[test]
1374 fn option_meet_with_some_meets_inner() {
1375 use std::collections::HashSet;
1376 let a: Option<HashSet<_>> = Some([1, 2, 3].into_iter().collect());
1377 let b: Option<HashSet<_>> = Some([2, 3, 4].into_iter().collect());
1378 let expected: Option<HashSet<_>> = Some([2, 3].into_iter().collect());
1379 assert_eq!(a.meet(&b), expected);
1380 }
1381
1382 #[test]
1383 fn option_top_is_some_top() {
1384 assert_eq!(Option::<bool>::top(), Some(true));
1385 }
1386
1387 #[test]
1388 fn tuple_meet_is_componentwise() {
1389 let a = (true, false);
1390 let b = (false, true);
1391 assert_eq!(a.meet(&b), (false, false));
1392 }
1393
1394 #[test]
1395 fn tuple_top_is_componentwise() {
1396 assert_eq!(<(bool, bool)>::top(), (true, true));
1397 }
1398
1399 // Tests for derive macros on tuple structs
1400 #[cfg(feature = "derive")]
1401 mod derive_tuple_tests {
1402 use super::*;
1403
1404 #[derive(Clone, Debug, PartialEq, Eq, Semigroup, Monoid)]
1405 struct TupleMonoid(Sum, HashSet<String>);
1406
1407 #[test]
1408 fn tuple_monoid_empty_works() {
1409 let empty = TupleMonoid::empty();
1410 assert_eq!(empty.0, Sum(0));
1411 assert_eq!(empty.1, HashSet::<String>::new());
1412 }
1413
1414 #[test]
1415 fn tuple_semigroup_combine_works() {
1416 let x = TupleMonoid(Sum(3), ["a".to_string()].into_iter().collect());
1417 let y = TupleMonoid(Sum(5), ["b".to_string()].into_iter().collect());
1418 let result = x.combine(&y);
1419 assert_eq!(result.0, Sum(8));
1420 assert_eq!(
1421 result.1,
1422 ["a".to_string(), "b".to_string()].into_iter().collect()
1423 );
1424 }
1425
1426 #[derive(Clone, Debug, PartialEq, Eq, JoinSemilattice, BoundedJoinSemilattice)]
1427 struct TupleLattice(HashSet<i32>, HashSet<String>);
1428
1429 #[test]
1430 fn tuple_lattice_bottom_works() {
1431 let bottom = TupleLattice::bottom();
1432 assert!(bottom.0.is_empty());
1433 assert!(bottom.1.is_empty());
1434 }
1435
1436 #[test]
1437 fn tuple_lattice_join_works() {
1438 let x = TupleLattice(
1439 [1, 2].into_iter().collect(),
1440 ["a".to_string()].into_iter().collect(),
1441 );
1442 let y = TupleLattice(
1443 [2, 3].into_iter().collect(),
1444 ["b".to_string()].into_iter().collect(),
1445 );
1446 let result = x.join(&y);
1447 assert_eq!(result.0, [1, 2, 3].into_iter().collect());
1448 assert_eq!(
1449 result.1,
1450 ["a".to_string(), "b".to_string()].into_iter().collect()
1451 );
1452 }
1453
1454 #[derive(Clone, Copy, Debug, PartialEq, Eq, Semigroup, Monoid, Group)]
1455 struct TupleGroup(AddInt, AddInt);
1456
1457 #[test]
1458 fn tuple_group_inverse_works() {
1459 let x = TupleGroup(AddInt(3), AddInt(5));
1460 let inv = x.inverse();
1461 assert_eq!(inv.0, AddInt(-3));
1462 assert_eq!(inv.1, AddInt(-5));
1463 assert_eq!(x.combine(&inv), TupleGroup::empty());
1464 }
1465 }
1466}