------------------------------------------------------------------------
-- The Agda standard library
--
-- Instances of algebraic structures made by taking two other instances
-- A and B, and having elements of the new instance be pairs |A| × |B|.
-- In mathematics, this would usually be written A × B or A ⊕ B.
--
-- From semigroups up, these new instances are products of the relevant
-- category. For structures with commutative addition (commutative
-- monoids, Abelian groups, semirings, rings), the direct product is
-- also the coproduct, making it a biproduct.
------------------------------------------------------------------------

{-# OPTIONS --cubical-compatible --safe #-}

module Algebra.Construct.DirectProduct where

open import Algebra
open import Data.Product.Base using (_×_; zip; _,_; map; _<*>_; uncurry)
open import Data.Product.Relation.Binary.Pointwise.NonDependent
open import Level using (Level; _⊔_)

private
  variable
    a b ℓ₁ ℓ₂ : Level

------------------------------------------------------------------------
-- Raw bundles

rawMagma : RawMagma a ℓ₁  RawMagma b ℓ₂  RawMagma (a  b) (ℓ₁  ℓ₂)
rawMagma M N = record
  { Carrier = M.Carrier × N.Carrier
  ; _≈_     = Pointwise M._≈_ N._≈_
  ; _∙_     = zip M._∙_ N._∙_
  } where module M = RawMagma M; module N = RawMagma N

rawMonoid : RawMonoid a ℓ₁  RawMonoid b ℓ₂  RawMonoid (a  b) (ℓ₁  ℓ₂)
rawMonoid M N = record
  { Carrier = M.Carrier × N.Carrier
  ; _≈_     = Pointwise M._≈_ N._≈_
  ; _∙_     = zip M._∙_ N._∙_
  ; ε       = M.ε , N.ε
  } where module M = RawMonoid M; module N = RawMonoid N

rawGroup : RawGroup a ℓ₁  RawGroup b ℓ₂  RawGroup (a  b) (ℓ₁  ℓ₂)
rawGroup G H = record
  { Carrier = G.Carrier × H.Carrier
  ; _≈_     = Pointwise G._≈_ H._≈_
  ; _∙_     = zip G._∙_ H._∙_
  ; ε       = G.ε , H.ε
  ; _⁻¹     = map G._⁻¹ H._⁻¹
  } where module G = RawGroup G; module H = RawGroup H

rawSemiring : RawSemiring a ℓ₁  RawSemiring b ℓ₂  RawSemiring (a  b) (ℓ₁  ℓ₂)
rawSemiring R S = record
  { Carrier = R.Carrier × S.Carrier
  ; _≈_     = Pointwise R._≈_ S._≈_
  ; _+_     = zip R._+_ S._+_
  ; _*_     = zip R._*_ S._*_
  ; 0#      = R.0# , S.0#
  ; 1#      = R.1# , S.1#
  } where module R = RawSemiring R; module S = RawSemiring S

rawRingWithoutOne : RawRingWithoutOne a ℓ₁  RawRingWithoutOne b ℓ₂  RawRingWithoutOne (a  b) (ℓ₁  ℓ₂)
rawRingWithoutOne R S = record
  { Carrier = R.Carrier × S.Carrier
  ; _≈_     = Pointwise R._≈_ S._≈_
  ; _+_     = zip R._+_ S._+_
  ; _*_     = zip R._*_ S._*_
  ; -_      = map R.-_ S.-_
  ; 0#      = R.0# , S.0#
  } where module R = RawRingWithoutOne R; module S = RawRingWithoutOne S

rawRing : RawRing a ℓ₁  RawRing b ℓ₂  RawRing (a  b) (ℓ₁  ℓ₂)
rawRing R S = record
  { Carrier = R.Carrier × S.Carrier
  ; _≈_     = Pointwise R._≈_ S._≈_
  ; _+_     = zip R._+_ S._+_
  ; _*_     = zip R._*_ S._*_
  ; -_      = map R.-_ S.-_
  ; 0#      = R.0# , S.0#
  ; 1#      = R.1# , S.1#
  } where module R = RawRing R; module S = RawRing S

rawQuasigroup : RawQuasigroup a ℓ₁  RawQuasigroup b ℓ₂  RawQuasigroup (a  b) (ℓ₁  ℓ₂)
rawQuasigroup M N = record
  { Carrier = M.Carrier × N.Carrier
  ; _≈_     = Pointwise M._≈_ N._≈_
  ; _∙_     = zip M._∙_ N._∙_
  ; _\\_    = zip M._\\_ N._\\_
  ; _//_    = zip M._//_ N._//_
  } where module M = RawQuasigroup M; module N = RawQuasigroup N

rawLoop : RawLoop a ℓ₁  RawLoop b ℓ₂  RawLoop (a  b) (ℓ₁  ℓ₂)
rawLoop M N = record
  { Carrier = M.Carrier × N.Carrier
  ; _≈_     = Pointwise M._≈_ N._≈_
  ; _∙_     = zip M._∙_ N._∙_
  ; _\\_    = zip M._\\_ N._\\_
  ; _//_    = zip M._//_ N._//_
  ; ε       = M.ε , N.ε
  } where module M = RawLoop M; module N = RawLoop N

------------------------------------------------------------------------
-- Bundles

magma : Magma a ℓ₁  Magma b ℓ₂  Magma (a  b) (ℓ₁  ℓ₂)
magma M N = record
  { Carrier = M.Carrier × N.Carrier
  ; _≈_     = Pointwise M._≈_ N._≈_
  ; _∙_     = zip M._∙_ N._∙_
  ; isMagma = record
    { isEquivalence = ×-isEquivalence M.isEquivalence N.isEquivalence
    ; ∙-cong = zip M.∙-cong N.∙-cong
    }
  } where module M = Magma M; module N = Magma N

idempotentMagma : IdempotentMagma a ℓ₁  IdempotentMagma b ℓ₂  IdempotentMagma (a  b) (ℓ₁  ℓ₂)
idempotentMagma G H = record
  { isIdempotentMagma = record
    { isMagma = Magma.isMagma (magma G.magma H.magma)
    ; idem = λ x  (G.idem , H.idem) <*> x
    }
  } where module G = IdempotentMagma G; module H = IdempotentMagma H

alternativeMagma : AlternativeMagma a ℓ₁  AlternativeMagma b ℓ₂  AlternativeMagma (a  b) (ℓ₁  ℓ₂)
alternativeMagma G H = record
  { isAlternativeMagma = record
    { isMagma = Magma.isMagma (magma G.magma H.magma)
    ; alter =  x y  G.alternativeˡ , H.alternativeˡ <*> x <*> y)
            ,  x y  G.alternativeʳ , H.alternativeʳ <*> x <*> y)
    }
  } where module G = AlternativeMagma G; module H = AlternativeMagma H

flexibleMagma : FlexibleMagma a ℓ₁  FlexibleMagma b ℓ₂  FlexibleMagma (a  b) (ℓ₁  ℓ₂)
flexibleMagma G H = record
  { isFlexibleMagma = record
    { isMagma = Magma.isMagma (magma G.magma H.magma)
    ; flex = λ x y  (G.flex , H.flex) <*> x <*> y
    }
  } where module G = FlexibleMagma G; module H = FlexibleMagma H

medialMagma : MedialMagma a ℓ₁  MedialMagma b ℓ₂  MedialMagma (a  b) (ℓ₁  ℓ₂)
medialMagma G H = record
  { isMedialMagma = record
    { isMagma = Magma.isMagma (magma G.magma H.magma)
    ; medial = λ x y u z  (G.medial , H.medial) <*> x <*> y <*> u <*> z
    }
  } where module G = MedialMagma G; module H = MedialMagma H

semimedialMagma : SemimedialMagma a ℓ₁  SemimedialMagma b ℓ₂  SemimedialMagma (a  b) (ℓ₁  ℓ₂)
semimedialMagma G H = record
  { isSemimedialMagma = record
    { isMagma = Magma.isMagma (magma G.magma H.magma)
    ; semiMedial =  x y z  G.semimedialˡ , H.semimedialˡ <*> x <*> y <*> z)
                 , ((λ x y z  G.semimedialʳ , H.semimedialʳ <*> x <*> y <*> z))
    }
  } where module G = SemimedialMagma G; module H = SemimedialMagma H

semigroup : Semigroup a ℓ₁  Semigroup b ℓ₂  Semigroup (a  b) (ℓ₁  ℓ₂)
semigroup G H = record
  { isSemigroup = record
    { isMagma = Magma.isMagma (magma G.magma H.magma)
    ; assoc = λ x y z  (G.assoc , H.assoc) <*> x <*> y <*> z
    }
  } where module G = Semigroup G; module H = Semigroup H

band : Band a ℓ₁  Band b ℓ₂  Band (a  b) (ℓ₁  ℓ₂)
band B C = record
  { isBand = record
    { isSemigroup = Semigroup.isSemigroup (semigroup B.semigroup C.semigroup)
    ; idem = λ x  (B.idem , C.idem) <*> x
    }
  } where module B = Band B; module C = Band C

commutativeSemigroup : CommutativeSemigroup a ℓ₁  CommutativeSemigroup b ℓ₂ 
                       CommutativeSemigroup (a  b) (ℓ₁  ℓ₂)
commutativeSemigroup G H = record
  { isCommutativeSemigroup = record
    { isSemigroup = Semigroup.isSemigroup (semigroup G.semigroup H.semigroup)
    ; comm = λ x y  (G.comm , H.comm) <*> x <*> y
    }
  } where module G = CommutativeSemigroup G; module H = CommutativeSemigroup H

unitalMagma : UnitalMagma a ℓ₁  UnitalMagma b ℓ₂  UnitalMagma (a  b) (ℓ₁  ℓ₂)
unitalMagma M N = record
  { ε = M.ε , N.ε
  ; isUnitalMagma = record
    { isMagma = Magma.isMagma (magma M.magma N.magma)
    ; identity = (M.identityˡ , N.identityˡ <*>_)
               , (M.identityʳ , N.identityʳ <*>_)
    }
  } where module M = UnitalMagma M; module N = UnitalMagma N

monoid : Monoid a ℓ₁  Monoid b ℓ₂  Monoid (a  b) (ℓ₁  ℓ₂)
monoid M N = record
  { ε = M.ε , N.ε
  ; isMonoid = record
    { isSemigroup = Semigroup.isSemigroup (semigroup M.semigroup N.semigroup)
    ; identity = (M.identityˡ , N.identityˡ <*>_)
               , (M.identityʳ , N.identityʳ <*>_)
    }
  } where module M = Monoid M; module N = Monoid N

commutativeMonoid : CommutativeMonoid a ℓ₁  CommutativeMonoid b ℓ₂ 
                    CommutativeMonoid (a  b) (ℓ₁  ℓ₂)
commutativeMonoid M N = record
  { isCommutativeMonoid = record
    { isMonoid = Monoid.isMonoid (monoid M.monoid N.monoid)
    ; comm = λ x y  (M.comm , N.comm) <*> x <*> y
    }
  } where module M = CommutativeMonoid M; module N = CommutativeMonoid N

idempotentCommutativeMonoid :
  IdempotentCommutativeMonoid a ℓ₁  IdempotentCommutativeMonoid b ℓ₂ 
  IdempotentCommutativeMonoid (a  b) (ℓ₁  ℓ₂)
idempotentCommutativeMonoid M N = record
  { isIdempotentCommutativeMonoid = record
    { isCommutativeMonoid = CommutativeMonoid.isCommutativeMonoid
      (commutativeMonoid M.commutativeMonoid N.commutativeMonoid)
    ; idem = λ x  (M.idem , N.idem) <*> x
    }
  }
  where
  module M = IdempotentCommutativeMonoid M
  module N = IdempotentCommutativeMonoid N

invertibleMagma : InvertibleMagma a ℓ₁  InvertibleMagma b ℓ₂  InvertibleMagma (a  b) (ℓ₁  ℓ₂)
invertibleMagma M N = record
  { _⁻¹ = map M._⁻¹ N._⁻¹
  ; isInvertibleMagma = record
    { isMagma = Magma.isMagma (magma M.magma N.magma)
    ; inverse =  x  (M.inverseˡ , N.inverseˡ) <*> x)
                ,  x  (M.inverseʳ , N.inverseʳ) <*> x)
    ; ⁻¹-cong = map M.⁻¹-cong N.⁻¹-cong
    }
  } where module M = InvertibleMagma M; module N = InvertibleMagma N

invertibleUnitalMagma : InvertibleUnitalMagma a ℓ₁  InvertibleUnitalMagma b ℓ₂  InvertibleUnitalMagma (a  b) (ℓ₁  ℓ₂)
invertibleUnitalMagma M N = record
  { ε = M.ε , N.ε
  ; isInvertibleUnitalMagma = record
    { isInvertibleMagma = InvertibleMagma.isInvertibleMagma (invertibleMagma M.invertibleMagma N.invertibleMagma)
    ; identity = (M.identityˡ , N.identityˡ <*>_)
               , (M.identityʳ , N.identityʳ <*>_)
    }
  } where module M = InvertibleUnitalMagma M; module N = InvertibleUnitalMagma N

group : Group a ℓ₁  Group b ℓ₂  Group (a  b) (ℓ₁  ℓ₂)
group G H = record
  { _⁻¹ = map G._⁻¹ H._⁻¹
  ; isGroup = record
    { isMonoid = Monoid.isMonoid (monoid G.monoid H.monoid)
    ; inverse =  x  (G.inverseˡ , H.inverseˡ) <*> x)
              ,  x  (G.inverseʳ , H.inverseʳ) <*> x)
    ; ⁻¹-cong = map G.⁻¹-cong H.⁻¹-cong
    }
  } where module G = Group G; module H = Group H

abelianGroup : AbelianGroup a ℓ₁  AbelianGroup b ℓ₂ 
               AbelianGroup (a  b) (ℓ₁  ℓ₂)
abelianGroup G H = record
  { isAbelianGroup = record
    { isGroup = Group.isGroup (group G.group H.group)
    ; comm = λ x y  (G.comm , H.comm) <*> x <*> y
    }
  } where module G = AbelianGroup G; module H = AbelianGroup H

semiringWithoutAnnihilatingZero : SemiringWithoutAnnihilatingZero a ℓ₁ 
                                  SemiringWithoutAnnihilatingZero b ℓ₂ 
                                  SemiringWithoutAnnihilatingZero (a  b) (ℓ₁  ℓ₂)
semiringWithoutAnnihilatingZero R S = record
  { isSemiringWithoutAnnihilatingZero = record
      { +-isCommutativeMonoid = CommutativeMonoid.isCommutativeMonoid
                                  (commutativeMonoid R.+-commutativeMonoid
                                                     S.+-commutativeMonoid)
      ; *-cong = zip R.*-cong S.*-cong
      ; *-assoc = λ x y z  (R.*-assoc , S.*-assoc) <*> x <*> y <*> z
      ; *-identity = (R.*-identityˡ , S.*-identityˡ <*>_)
                   , (R.*-identityʳ , S.*-identityʳ <*>_)
      ; distrib    =  x y z  (R.distribˡ , S.distribˡ) <*> x <*> y <*> z)
                   ,  x y z  (R.distribʳ , S.distribʳ) <*> x <*> y <*> z)
      }
  } where module R = SemiringWithoutAnnihilatingZero R
          module S = SemiringWithoutAnnihilatingZero S

semiring : Semiring a ℓ₁  Semiring b ℓ₂  Semiring (a  b) (ℓ₁  ℓ₂)
semiring R S = record
  { isSemiring = record
      { isSemiringWithoutAnnihilatingZero =
          SemiringWithoutAnnihilatingZero.isSemiringWithoutAnnihilatingZero U
      ; zero = uncurry  x y  R.zeroˡ x , S.zeroˡ y)
             , uncurry  x y  R.zeroʳ x , S.zeroʳ y)
      }
  }
  where
  module R = Semiring R
  module S = Semiring S
  U = semiringWithoutAnnihilatingZero R.semiringWithoutAnnihilatingZero
                                      S.semiringWithoutAnnihilatingZero

commutativeSemiring : CommutativeSemiring a ℓ₁  CommutativeSemiring b ℓ₂ 
                      CommutativeSemiring (a  b) (ℓ₁  ℓ₂)
commutativeSemiring R S = record
  { isCommutativeSemiring = record
      { isSemiring = Semiring.isSemiring (semiring R.semiring S.semiring)
      ; *-comm     = λ x y  (R.*-comm , S.*-comm) <*> x <*> y
      }
  } where module R = CommutativeSemiring R;  module S = CommutativeSemiring S

idempotentSemiring : IdempotentSemiring a ℓ₁  IdempotentSemiring b ℓ₂  IdempotentSemiring (a  b) (ℓ₁  ℓ₂)
idempotentSemiring K L = record
  { isIdempotentSemiring = record
      { isSemiring = Semiring.isSemiring (semiring K.semiring L.semiring)
      ; +-idem = λ x  (K.+-idem , L.+-idem) <*> x
      }
  } where module K = IdempotentSemiring K;  module L = IdempotentSemiring L

kleeneAlgebra : KleeneAlgebra a ℓ₁  KleeneAlgebra b ℓ₂  KleeneAlgebra (a  b) (ℓ₁  ℓ₂)
kleeneAlgebra K L = record
  { isKleeneAlgebra = record
      { isIdempotentSemiring = IdempotentSemiring.isIdempotentSemiring (idempotentSemiring K.idempotentSemiring L.idempotentSemiring)
      ; starExpansive =  x  (K.starExpansiveˡ  , L.starExpansiveˡ) <*> x)
                      ,  x  (K.starExpansiveʳ  , L.starExpansiveʳ) <*> x)
      ; starDestructive =  a b x x₁  (K.starDestructiveˡ  , L.starDestructiveˡ) <*> a <*> b <*> x <*> x₁)
                        ,  a b x x₁  (K.starDestructiveʳ  , L.starDestructiveʳ) <*> a <*> b <*> x <*> x₁)
      }
  } where module K = KleeneAlgebra K;  module L = KleeneAlgebra L

ringWithoutOne : RingWithoutOne a ℓ₁  RingWithoutOne b ℓ₂  RingWithoutOne (a  b) (ℓ₁  ℓ₂)
ringWithoutOne R S = record
  { isRingWithoutOne = record
      { +-isAbelianGroup = AbelianGroup.isAbelianGroup ((abelianGroup R.+-abelianGroup S.+-abelianGroup))
      ; *-cong           = Semigroup.∙-cong (semigroup R.*-semigroup S.*-semigroup)
      ; *-assoc          = Semigroup.assoc (semigroup R.*-semigroup S.*-semigroup)
      ; distrib          =  x y z  (R.distribˡ , S.distribˡ) <*> x <*> y <*> z)
                            ,  x y z  (R.distribʳ , S.distribʳ) <*> x <*> y <*> z)
      }

  } where module R = RingWithoutOne R; module S = RingWithoutOne S

nonAssociativeRing : NonAssociativeRing a ℓ₁  NonAssociativeRing b ℓ₂  NonAssociativeRing (a  b) (ℓ₁  ℓ₂)
nonAssociativeRing R S = record
  { isNonAssociativeRing = record
      { +-isAbelianGroup = AbelianGroup.isAbelianGroup ((abelianGroup R.+-abelianGroup S.+-abelianGroup))
      ; *-cong           = UnitalMagma.∙-cong (unitalMagma R.*-unitalMagma S.*-unitalMagma)
      ; *-identity       = UnitalMagma.identity (unitalMagma R.*-unitalMagma S.*-unitalMagma)
      ; distrib          =  x y z  (R.distribˡ , S.distribˡ) <*> x <*> y <*> z)
                            ,  x y z  (R.distribʳ , S.distribʳ) <*> x <*> y <*> z)
      ; zero             = uncurry  x y  R.zeroˡ x , S.zeroˡ y)
                            , uncurry  x y  R.zeroʳ x , S.zeroʳ y)
      }

  } where module R = NonAssociativeRing R; module S = NonAssociativeRing S

quasiring : Quasiring a ℓ₁  Quasiring b ℓ₂  Quasiring (a  b) (ℓ₁  ℓ₂)
quasiring R S = record
  { isQuasiring = record
      { +-isMonoid = Monoid.isMonoid ((monoid R.+-monoid S.+-monoid))
      ; *-cong           = Monoid.∙-cong (monoid R.*-monoid S.*-monoid)
      ; *-assoc          = Monoid.assoc (monoid R.*-monoid S.*-monoid)
      ; *-identity       = Monoid.identity ((monoid R.*-monoid S.*-monoid))
      ; distrib          =  x y z  (R.distribˡ , S.distribˡ) <*> x <*> y <*> z)
                            ,  x y z  (R.distribʳ , S.distribʳ) <*> x <*> y <*> z)
      ; zero             = uncurry  x y  R.zeroˡ x , S.zeroˡ y)
                            , uncurry  x y  R.zeroʳ x , S.zeroʳ y)
      }

  } where module R = Quasiring R; module S = Quasiring S

nearring : Nearring a ℓ₁  Nearring b ℓ₂  Nearring (a  b) (ℓ₁  ℓ₂)
nearring R S =  record
  { isNearring = record
      { isQuasiring = Quasiring.isQuasiring (quasiring R.quasiring S.quasiring)
      ; +-inverse   =  x  (R.+-inverseˡ , S.+-inverseˡ) <*> x)
                      ,  x  (R.+-inverseʳ , S.+-inverseʳ) <*> x)
      ; ⁻¹-cong     = map R.⁻¹-cong S.⁻¹-cong
      }
  } where module R = Nearring R; module S = Nearring S

ring : Ring a ℓ₁  Ring b ℓ₂  Ring (a  b) (ℓ₁  ℓ₂)
ring R S = record
  { -_     = uncurry  x y  R.-_ x , S.-_ y)
  ; isRing = record
      { +-isAbelianGroup = AbelianGroup.isAbelianGroup A
      ; *-cong           = Semiring.*-cong Semi
      ; *-assoc          = Semiring.*-assoc Semi
      ; *-identity       = Semiring.*-identity Semi
      ; distrib          = Semiring.distrib Semi
      }
  }
  where
  module R = Ring R
  module S = Ring S
  Semi = semiring R.semiring S.semiring
  A    = abelianGroup R.+-abelianGroup S.+-abelianGroup

commutativeRing : CommutativeRing a ℓ₁  CommutativeRing b ℓ₂ 
                  CommutativeRing (a  b) (ℓ₁  ℓ₂)
commutativeRing R S = record
  { isCommutativeRing = record
      { isRing = Ring.isRing (ring R.ring S.ring)
      ; *-comm = λ x y  (R.*-comm , S.*-comm) <*> x <*> y
      }
  } where module R = CommutativeRing R; module S = CommutativeRing S

quasigroup : Quasigroup a ℓ₁  Quasigroup b ℓ₂  Quasigroup (a  b) (ℓ₁  ℓ₂)
quasigroup M N = record
  { _\\_    = zip M._\\_ N._\\_
  ; _//_    = zip M._//_ N._//_
  ; isQuasigroup = record
    { isMagma = Magma.isMagma (magma M.magma N.magma)
    ; \\-cong = zip M.\\-cong N.\\-cong
    ; //-cong = zip M.//-cong N.//-cong
    ; leftDivides =  x y  M.leftDividesˡ , N.leftDividesˡ <*> x <*> y) ,  x y  M.leftDividesʳ , N.leftDividesʳ <*> x <*> y)
    ; rightDivides =  x y  M.rightDividesˡ , N.rightDividesˡ <*> x <*> y) ,  x y  M.rightDividesʳ , N.rightDividesʳ <*> x <*> y)
    }
  } where module M = Quasigroup M; module N = Quasigroup N

loop : Loop a ℓ₁  Loop b ℓ₂  Loop (a  b) (ℓ₁  ℓ₂)
loop M N = record
  { ε = M.ε , N.ε
  ; isLoop = record
    { isQuasigroup = Quasigroup.isQuasigroup (quasigroup M.quasigroup N.quasigroup)
    ; identity = (M.identityˡ , N.identityˡ <*>_)
               , (M.identityʳ , N.identityʳ <*>_)
    }
  } where module M = Loop M; module N = Loop N

leftBolLoop : LeftBolLoop a ℓ₁  LeftBolLoop b ℓ₂  LeftBolLoop (a  b) (ℓ₁  ℓ₂)
leftBolLoop M N = record
  { isLeftBolLoop = record
    { isLoop = Loop.isLoop (loop M.loop N.loop)
    ; leftBol = λ x y z  M.leftBol , N.leftBol <*> x <*> y <*> z
    }
  } where module M = LeftBolLoop M; module N = LeftBolLoop N

rightBolLoop : RightBolLoop a ℓ₁  RightBolLoop b ℓ₂  RightBolLoop (a  b) (ℓ₁  ℓ₂)
rightBolLoop M N = record
  { isRightBolLoop = record
    { isLoop = Loop.isLoop (loop M.loop N.loop)
    ; rightBol = λ x y z  M.rightBol , N.rightBol <*> x <*> y <*> z
    }
  } where module M = RightBolLoop M; module N = RightBolLoop N

middleBolLoop : MiddleBolLoop a ℓ₁  MiddleBolLoop b ℓ₂  MiddleBolLoop (a  b) (ℓ₁  ℓ₂)
middleBolLoop M N = record
  { isMiddleBolLoop = record
    { isLoop = Loop.isLoop (loop M.loop N.loop)
    ; middleBol = λ x y z  M.middleBol , N.middleBol <*> x <*> y <*> z
    }
  } where module M = MiddleBolLoop M; module N = MiddleBolLoop N

moufangLoop : MoufangLoop a ℓ₁  MoufangLoop b ℓ₂  MoufangLoop (a  b) (ℓ₁  ℓ₂)
moufangLoop M N = record
  { isMoufangLoop = record
    { isLeftBolLoop = LeftBolLoop.isLeftBolLoop (leftBolLoop M.leftBolLoop N.leftBolLoop)
    ; rightBol = λ x y z  M.rightBol , N.rightBol <*> x <*> y <*> z
    ; identical = λ x y z  M.identical , N.identical <*> x <*> y <*> z
    }
  } where module M = MoufangLoop M; module N = MoufangLoop N