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.