I would like to contrast two styles for declaring new record types for algebraic structures in Agda.
Following the style used in the standard Agda package Algebra, one might define BoundedJoinSemilattice as follows:
record IsBoundedJoinSemilattice {a ℓ} {A : Set a}
(_≈_ : Rel A ℓ) (_∨_ : Op₂ A) (⊥ : A) : Set (a Level.⊔ ℓ) where
field
isCommutativeMonoid : IsCommutativeMonoid _≈_ _∨_ ⊥
idem : Idempotent _≈_ _∨_
open IsCommutativeMonoid isCommutativeMonoid public
record BoundedJoinSemilattice c ℓ : Set (suc (c ⊔ ℓ)) where
infixr 6 _∨_
infix 4 _≈_
field
Carrier : Set c
_≈_ : Rel Carrier ℓ
_∨_ : Op₂ Carrier
⊥ : Carrier
isBoundedJoinSemilattice : IsBoundedJoinSemilattice _≈_ _∨_ ⊥
open IsBoundedJoinSemilattice isBoundedJoinSemilattice public
commutativeMonoid : CommutativeMonoid _ _
commutativeMonoid = record {
Carrier = Carrier;
_≈_ = _≈_;
_∙_ = _∨_;
ε = ⊥;
isCommutativeMonoid = isCommutativeMonoid
}
With this approach, any fields of BoundedJoinSemiLattice that overlap (up to renaming) with those of other more abstract structures such as Setoid, Semigroup, Monoid and CommutativeMonoid are duplicated in BoundedJoinSemiLattice. In order to view a BoundedJoinSemiLattice as one of its "supertypes", one must call a projection function responsible for mapping its fields to those of the supertype, such as the commutativeMonoid function above.
However, this field duplication can lead to significant boilerplate in code that builds more specific algebraic structures out of less specific ones. A more natural definition might be something like this (renaming CommutativeMonoid to CM):
record IsBoundedJoinSemilattice {c ℓ} (cm : CM c ℓ)
(⊥ : CM.Carrier cm) : Set (c Level.⊔ ℓ) where
field
isCommutativeMonoid : IsCM (CM._≈_ cm) (CM._∙_ cm) ⊥
idem : Idempotent (CM._≈_ cm) (CM._∙_ cm)
open IsCommutativeMonoid isCommutativeMonoid public
record BoundedJoinSemilattice c ℓ : Set (suc (c ⊔ ℓ)) where
field
commutativeMonoid : CM c ℓ
isBoundedJoinSemilattice : IsBoundedJoinSemilattice commutativeMonoid (CM.ε commutativeMonoid)
open CommutativeMonoid commutativeMonoid public using (Carrier; _≈_) renaming (
_∙_ to _∨_;
ε to ⊥
)
open IsBoundedJoinSemilattice isBoundedJoinSemilattice public
Here, the idea is not to duplicate the fields of CommutativeMonoid into BoundedJoinSemilattice, but rather for the latter to declare a single field of type CommutativeMonoid. We then use an open...public to "inherit" its sub-fields into the containing record. Indeed, this is exactly the idiom used elsewhere in the standard library in Algebra.Structures, except that here we also rename the inherited fields, so that they are appropriately named in the inheriting context.
Not only is this second approach less redundant, but now client code that wants to construct a BoundedJoinSemilattice from a CommutativeMonoid can simply pass it as an argument to the record being constructed. On the other hand, client code that wants to construct a BoundedJoinSemilattice from scratch must now construct an intermediate CommutativeMonoid.
Is there a reason that the Algebra module doesn't use this inheritance style, but Algebra.Structures does? Maybe there are problems with the second approach that I haven't spotted, or perhaps it doesn't make much difference: for example with the first approach, maybe one can simply define a "constructor" function which handles the construction of a BoundedJoinSemiLattice from a CommutativeMonoid, to recover much of the convenience of the second approach.