{-# OPTIONS --without-K --exact-split --safe #-}

module Fragment.Extensions.CSemigroup.Base where

open import Fragment.Equational.Theory.Bundles

open import Fragment.Algebra.Signature
open import Fragment.Algebra.Free Σ-magma hiding (_~_)
open import Fragment.Algebra.Homomorphism Σ-magma
open import Fragment.Algebra.Algebra Σ-magma
  using (Algebra; IsAlgebra; Interpretation; Congruence)

open import Fragment.Equational.FreeExtension Θ-csemigroup
  using (FreeExtension; IsFreeExtension)
open import Fragment.Equational.Model Θ-csemigroup
open import Fragment.Extensions.CSemigroup.Monomial

open import Fragment.Setoid.Morphism using (_↝_)

open import Level using (Level; _⊔_)

open import Data.Nat using (ℕ)
open import Data.Fin using (#_)

open import Data.Vec using (Vec; []; _∷_; map)
open import Data.Vec.Relation.Binary.Pointwise.Inductive using ([]; _∷_)

import Relation.Binary.Reasoning.Setoid as Reasoning
open import Relation.Binary using (Setoid; IsEquivalence)
open import Relation.Binary.PropositionalEquality as PE using (_≡_)

private
  variable
    a ℓ : Level

module _ (A : Model {a} {ℓ}) (n : ℕ) where

  private

    open module A = Setoid ∥ A ∥/≈

    _·_ : ∥ A ∥ → ∥ A ∥ → ∥ A ∥
    x · y = A ⟦ • ⟧ (x ∷ y ∷ [])

    ·-cong : ∀ {x y z w} → x ≈ y → z ≈ w → x · z ≈ y · w
    ·-cong x≈y z≈w = (A ⟦ • ⟧-cong) (x≈y ∷ z≈w ∷ [])

    ·-comm : ∀ (x y : ∥ A ∥) → x · y ≈ y · x
    ·-comm x y = ∥ A ∥ₐ-models comm (env {A = ∥ A ∥ₐ} (x ∷ y ∷ []))

    ·-assoc : ∀ (x y z : ∥ A ∥) → (x · y) · z ≈ x · (y · z)
    ·-assoc x y z = ∥ A ∥ₐ-models assoc (env {A = ∥ A ∥ₐ} (x ∷ y ∷ z ∷ []))

    data Blob : Set a where
      sta  : ∥ A ∥ → Blob
      dyn  : ∥ J' n ∥ → Blob
      blob : ∥ J' n ∥ → ∥ A ∥ → Blob

    infix 6 _≋_

    data _≋_ : Blob → Blob → Set (a ⊔ ℓ) where
      sta  : ∀ {x y} → x ≈ y → sta x ≋ sta y
      dyn  : ∀ {xs ys} → xs ≡ ys → dyn xs ≋ dyn ys
      blob : ∀ {x y xs ys} → xs ≡ ys → x ≈ y
             → blob xs x ≋ blob ys y

    ≋-refl : ∀ {x} → x ≋ x
    ≋-refl {x = sta x}     = sta A.refl
    ≋-refl {x = dyn xs}    = dyn PE.refl
    ≋-refl {x = blob xs x} = blob PE.refl A.refl

    ≋-sym : ∀ {x y} → x ≋ y → y ≋ x
    ≋-sym (sta p)     = sta (A.sym p)
    ≋-sym (dyn ps)    = dyn (PE.sym ps)
    ≋-sym (blob ps p) = blob (PE.sym ps) (A.sym p)

    ≋-trans : ∀ {x y z} → x ≋ y → y ≋ z → x ≋ z
    ≋-trans (sta p)     (sta q)     = sta (A.trans p q)
    ≋-trans (dyn ps)    (dyn qs)    = dyn (PE.trans ps qs)
    ≋-trans (blob ps p) (blob qs q) = blob (PE.trans ps qs) (A.trans p q)

  ≋-isEquivalence : IsEquivalence _≋_
  ≋-isEquivalence = record { refl  = ≋-refl
                           ; sym   = ≋-sym
                           ; trans = ≋-trans
                           }

  Blob/≋ : Setoid _ _
  Blob/≋ = record { Carrier       = Blob
                  ; _≈_           = _≋_
                  ; isEquivalence = ≋-isEquivalence
                  }

  _++_ : Blob → Blob → Blob
  sta x     ++ sta y     = sta (x · y)
  dyn xs    ++ dyn ys    = dyn (xs ⊗ ys)
  sta x     ++ dyn ys    = blob ys        x
  sta x     ++ blob ys y = blob ys        (x · y)
  dyn xs    ++ sta y     = blob xs        y
  dyn xs    ++ blob ys y = blob (xs ⊗ ys) y
  blob xs x ++ sta y     = blob xs        (x · y)
  blob xs x ++ dyn ys    = blob (xs ⊗ ys) x
  blob xs x ++ blob ys y = blob (xs ⊗ ys) (x · y)

  ++-cong : ∀ {x y z w} → x ≋ y → z ≋ w → x ++ z ≋ y ++ w
  ++-cong (sta p)     (sta q)     = sta (·-cong p q)
  ++-cong (dyn ps)    (dyn qs)    = dyn (⊗-cong ps qs)
  ++-cong (sta p)     (dyn qs)    = blob qs p
  ++-cong (sta p)     (blob qs q) = blob qs (·-cong p q)
  ++-cong (dyn ps)    (sta q)     = blob ps q
  ++-cong (dyn ps)    (blob qs q) = blob (⊗-cong ps qs) q
  ++-cong (blob ps p) (sta q)     = blob ps (·-cong p q)
  ++-cong (blob ps p) (dyn qs)    = blob (⊗-cong ps qs) p
  ++-cong (blob ps p) (blob qs q) = blob (⊗-cong ps qs) (·-cong p q)

  ++-comm : ∀ x y → x ++ y ≋ y ++ x
  ++-comm (sta x)     (sta y)     = sta (·-comm x y)
  ++-comm (dyn xs)    (dyn ys)    = dyn (⊗-comm xs ys)
  ++-comm (sta x)     (dyn ys)    = ≋-refl
  ++-comm (sta x)     (blob ys y) = blob PE.refl (·-comm x y)
  ++-comm (dyn xs)    (sta y)     = ≋-refl
  ++-comm (dyn xs)    (blob ys y) = blob (⊗-comm xs ys) A.refl
  ++-comm (blob xs x) (sta y)     = blob PE.refl (·-comm x y)
  ++-comm (blob xs x) (dyn ys)    = blob (⊗-comm xs ys) A.refl
  ++-comm (blob xs x) (blob ys y) = blob (⊗-comm xs ys) (·-comm x y)

  ++-assoc : ∀ x y z → (x ++ y) ++ z ≋ x ++ (y ++ z)
  ++-assoc (sta x)     (sta y)     (sta z)     = sta (·-assoc x y z)
  ++-assoc (dyn xs)    (dyn ys)    (dyn zs)    = dyn (⊗-assoc xs ys zs)
  ++-assoc (sta x)     (sta y)     (dyn zs)    = ≋-refl
  ++-assoc (sta x)     (sta y)     (blob zs z) = blob PE.refl (·-assoc x y z)
  ++-assoc (sta x)     (dyn ys)    (sta z)     = ≋-refl
  ++-assoc (sta x)     (dyn ys)    (dyn zs)    = ≋-refl
  ++-assoc (sta x)     (dyn ys)    (blob zs z) = ≋-refl
  ++-assoc (sta x)     (blob ys y) (sta z)     = blob PE.refl (·-assoc x y z)
  ++-assoc (sta x)     (blob ys y) (dyn zs)    = ≋-refl
  ++-assoc (sta x)     (blob ys y) (blob zs z) = blob PE.refl (·-assoc x y z)
  ++-assoc (dyn xs)    (sta y)     (sta z)     = ≋-refl
  ++-assoc (dyn xs)    (sta y)     (dyn zs)    = ≋-refl
  ++-assoc (dyn xs)    (sta y)     (blob zs z) = ≋-refl
  ++-assoc (dyn xs)    (dyn ys)    (sta z)     = ≋-refl
  ++-assoc (dyn xs)    (dyn ys)    (blob zs z) = blob (⊗-assoc xs ys zs) A.refl
  ++-assoc (dyn xs)    (blob ys y) (sta z)     = ≋-refl
  ++-assoc (dyn xs)    (blob ys y) (dyn zs)    = blob (⊗-assoc xs ys zs) A.refl
  ++-assoc (dyn xs)    (blob ys y) (blob zs z) = blob (⊗-assoc xs ys zs) A.refl
  ++-assoc (blob xs x) (sta y)     (sta z)     = blob PE.refl (·-assoc x y z)
  ++-assoc (blob xs x) (sta y)     (dyn zs)    = ≋-refl
  ++-assoc (blob xs x) (sta y)     (blob zs z) = blob PE.refl (·-assoc x y z)
  ++-assoc (blob xs x) (dyn ys)    (sta z)     = ≋-refl
  ++-assoc (blob xs x) (dyn ys)    (dyn zs)    = blob (⊗-assoc xs ys zs) A.refl
  ++-assoc (blob xs x) (dyn ys)    (blob zs z) = blob (⊗-assoc xs ys zs) A.refl
  ++-assoc (blob xs x) (blob ys y) (sta z)     = blob PE.refl (·-assoc x y z)
  ++-assoc (blob xs x) (blob ys y) (dyn zs)    = blob (⊗-assoc xs ys zs) A.refl
  ++-assoc (blob xs x) (blob ys y) (blob zs z) = blob (⊗-assoc xs ys zs) (·-assoc x y z)

  Blob⟦_⟧ : Interpretation Blob/≋
  Blob⟦ • ⟧ (x ∷ y ∷ []) = x ++ y

  Blob⟦_⟧-cong : Congruence Blob/≋ Blob⟦_⟧
  Blob⟦ • ⟧-cong (p ∷ q ∷ []) = ++-cong p q

  Blob/≋-isAlgebra : IsAlgebra Blob/≋
  Blob/≋-isAlgebra = record { ⟦_⟧     = Blob⟦_⟧
                            ; ⟦⟧-cong = Blob⟦_⟧-cong
                            }

  Blob/≋-algebra : Algebra
  Blob/≋-algebra =
    record { ∥_∥/≈           = Blob/≋
           ; ∥_∥/≈-isAlgebra = Blob/≋-isAlgebra
           }

  Blob/≋-models : Models Blob/≋-algebra
  Blob/≋-models comm θ  = ++-comm (θ (# 0)) (θ (# 1))
  Blob/≋-models assoc θ = ++-assoc (θ (# 0)) (θ (# 1)) (θ (# 2))

  Blob/≋-isModel : IsModel Blob/≋
  Blob/≋-isModel =
    record { isAlgebra = Blob/≋-isAlgebra
           ; models    = Blob/≋-models
           }

  Frex : Model
  Frex = record { ∥_∥/≈   = Blob/≋
                ; isModel = Blob/≋-isModel
                }

  ∣inl∣ : ∥ A ∥ → ∥ Frex ∥
  ∣inl∣ = sta

  ∣inl∣-cong : Congruent _≈_ _≋_ ∣inl∣
  ∣inl∣-cong = sta

  ∣inl∣⃗ : ∥ A ∥/≈ ↝ ∥ Frex ∥/≈
  ∣inl∣⃗ = record { ∣_∣      = ∣inl∣
                  ; ∣_∣-cong = ∣inl∣-cong
                  }

  ∣inl∣-hom : Homomorphic ∥ A ∥ₐ ∥ Frex ∥ₐ ∣inl∣
  ∣inl∣-hom • (x ∷ y ∷ []) = ≋-refl

  inl : ∥ A ∥ₐ ⟿ ∥ Frex ∥ₐ
  inl = record { ∣_∣⃗    = ∣inl∣⃗
               ; ∣_∣-hom = ∣inl∣-hom
               }

  ∣inr∣ : ∥ J n ∥ → ∥ Frex ∥
  ∣inr∣ x = dyn (∣ norm ∣ x)

  ∣inr∣-cong : Congruent ≈[ J n ] _≋_ ∣inr∣
  ∣inr∣-cong p = dyn (∣ norm ∣-cong p)

  ∣inr∣⃗ : ∥ J n ∥/≈ ↝ ∥ Frex ∥/≈
  ∣inr∣⃗ = record { ∣_∣      = ∣inr∣
                  ; ∣_∣-cong = ∣inr∣-cong
                  }

  ∣inr∣-hom : Homomorphic ∥ J n ∥ₐ ∥ Frex ∥ₐ ∣inr∣
  ∣inr∣-hom • (x ∷ y ∷ []) = dyn (∣ norm ∣-hom • (x ∷ y ∷ []))


  inr : ∥ J n ∥ₐ ⟿ ∥ Frex ∥ₐ
  inr = record { ∣_∣⃗    = ∣inr∣⃗
               ; ∣_∣-hom = ∣inr∣-hom
               }

  module _ {b ℓ} (X : Model {b} {ℓ}) where

    private

      open module X = Setoid ∥ X ∥/≈ renaming (_≈_ to _~_)

      _⊕_ : ∥ X ∥ → ∥ X ∥ → ∥ X ∥
      x ⊕ y = X ⟦ • ⟧ (x ∷ y ∷ [])

      ⊕-cong : ∀ {x y z w} → x ~ y → z ~ w → x ⊕ z ~ y ⊕ w
      ⊕-cong p q = (X ⟦ • ⟧-cong) (p ∷ q ∷ [])

      ⊕-comm : ∀ (x y : ∥ X ∥) → x ⊕ y ~ y ⊕ x
      ⊕-comm x y = ∥ X ∥ₐ-models comm (env {A = ∥ X ∥ₐ} (x ∷ y ∷ []))

      ⊕-assoc : ∀ (x y z : ∥ X ∥) → (x ⊕ y) ⊕ z ~ x ⊕ (y ⊕ z)
      ⊕-assoc x y z = ∥ X ∥ₐ-models assoc (env {A = ∥ X ∥ₐ} (x ∷ y ∷ z ∷ []))

    module _
      (f : ∥ A ∥ₐ ⟿ ∥ X ∥ₐ)
      (g : ∥ J n ∥ₐ ⟿ ∥ X ∥ₐ)
      where

      ∣resid∣ : ∥ Frex ∥ → ∥ X ∥
      ∣resid∣ (sta x)     = ∣ f ∣ x
      ∣resid∣ (dyn xs)    = ∣ g ∣ (∣ syn ∣ xs)
      ∣resid∣ (blob xs x) = ∣ g ∣ (∣ syn ∣ xs) ⊕ ∣ f ∣ x

      ∣resid∣-cong : Congruent _≋_ _~_ ∣resid∣
      ∣resid∣-cong (sta p)     = ∣ f ∣-cong p
      ∣resid∣-cong (dyn ps)    = ∣ g ∣-cong (∣ syn ∣-cong ps)
      ∣resid∣-cong (blob ps p) =
        ⊕-cong (∣ g ∣-cong (∣ syn ∣-cong ps)) (∣ f ∣-cong p)

      open Reasoning ∥ X ∥/≈

      ∣resid∣-hom : Homomorphic ∥ Frex ∥ₐ ∥ X ∥ₐ ∣resid∣
      ∣resid∣-hom • (sta x     ∷ sta y     ∷ []) = ∣ f ∣-hom • (x ∷ y ∷ [])
      ∣resid∣-hom • (sta x     ∷ dyn ys    ∷ []) = ⊕-comm (∣ f ∣ x) _
      ∣resid∣-hom • (sta x     ∷ blob ys y ∷ []) = begin
          ∣ f ∣ x ⊕ (∣ g ∣ (∣ syn ∣ ys) ⊕ ∣ f ∣ y)
        ≈⟨ ⊕-cong X.refl (⊕-comm _ (∣ f ∣ y)) ⟩
          ∣ f ∣ x ⊕ (∣ f ∣ y ⊕ ∣ g ∣ (∣ syn ∣ ys))
        ≈⟨ X.sym (⊕-assoc (∣ f ∣ x) (∣ f ∣ y) _) ⟩
          (∣ f ∣ x ⊕ ∣ f ∣ y) ⊕ ∣ g ∣ (∣ syn ∣ ys)
        ≈⟨ ⊕-cong (∣ f ∣-hom • (x ∷ y ∷ [])) X.refl ⟩
          ∣ f ∣ (x · y) ⊕ ∣ g ∣ (∣ syn ∣ ys)
        ≈⟨ ⊕-comm _ _ ⟩
          ∣ g ∣ (∣ syn ∣ ys) ⊕ ∣ f ∣ (x · y)
        ∎
      ∣resid∣-hom • (dyn xs    ∷ sta y     ∷ []) = X.refl
      ∣resid∣-hom • (dyn xs    ∷ dyn ys    ∷ []) = ∣ g ⊙ syn ∣-hom • (xs ∷ ys ∷ [])
      ∣resid∣-hom • (dyn xs    ∷ blob ys y ∷ []) = begin
          ∣ g ∣ (∣ syn ∣ xs) ⊕ (∣ g ∣ (∣ syn ∣ ys) ⊕ ∣ f ∣ y)
        ≈⟨ X.sym (⊕-assoc _ _ (∣ f ∣ y)) ⟩
          (∣ g ∣ (∣ syn ∣ xs) ⊕ ∣ g ∣ (∣ syn ∣ ys)) ⊕ ∣ f ∣ y
        ≈⟨ ⊕-cong (∣ g ⊙ syn ∣-hom • (xs ∷ ys ∷ [])) X.refl ⟩
          ∣ g ∣ (∣ syn ∣ (xs ⊗ ys)) ⊕ ∣ f ∣ y
        ∎
      ∣resid∣-hom • (blob xs x ∷ sta y     ∷ []) = begin
          (∣ g ∣ (∣ syn ∣ xs) ⊕ ∣ f ∣ x) ⊕ ∣ f ∣ y
        ≈⟨ ⊕-assoc _ (∣ f ∣ x) (∣ f ∣ y) ⟩
          ∣ g ∣ (∣ syn ∣ xs) ⊕ (∣ f ∣ x ⊕ ∣ f ∣ y)
        ≈⟨ ⊕-cong X.refl (∣ f ∣-hom • (x ∷ y ∷ [])) ⟩
          ∣ g ∣ (∣ syn ∣ xs) ⊕ ∣ f ∣ (x · y)
        ∎
      ∣resid∣-hom • (blob xs x ∷ dyn ys    ∷ []) = begin
          (∣ g ∣ (∣ syn ∣ xs) ⊕ ∣ f ∣ x) ⊕ ∣ g ∣ (∣ syn ∣ ys)
        ≈⟨ ⊕-assoc _ (∣ f ∣ x) _ ⟩
          ∣ g ∣ (∣ syn ∣ xs) ⊕ (∣ f ∣ x ⊕ ∣ g ∣ (∣ syn ∣ ys))
        ≈⟨ ⊕-cong X.refl (⊕-comm (∣ f ∣ x) _) ⟩
          ∣ g ∣ (∣ syn ∣ xs) ⊕ (∣ g ∣ (∣ syn ∣ ys) ⊕ ∣ f ∣ x)
        ≈⟨ X.sym (⊕-assoc _ _ (∣ f ∣ x)) ⟩
          (∣ g ∣ (∣ syn ∣ xs) ⊕ ∣ g ∣ (∣ syn ∣ ys)) ⊕ ∣ f ∣ x
        ≈⟨ ⊕-cong (∣ g ⊙ syn ∣-hom • (xs ∷ ys ∷ [])) X.refl ⟩
          ∣ g ∣ (∣ syn ∣ (xs ⊗ ys)) ⊕ ∣ f ∣ x
        ∎
      ∣resid∣-hom • (blob xs x ∷ blob ys y ∷ []) = begin
          (∣ g ∣ (∣ syn ∣ xs) ⊕ ∣ f ∣ x) ⊕ (∣ g ∣ (∣ syn ∣ ys) ⊕ ∣ f ∣ y)
        ≈⟨ ⊕-assoc _ (∣ f ∣ x) _ ⟩
          ∣ g ∣ (∣ syn ∣ xs) ⊕ (∣ f ∣ x ⊕ (∣ g ∣ (∣ syn ∣ ys) ⊕ ∣ f ∣ y))
        ≈⟨ ⊕-cong X.refl (X.sym (⊕-assoc (∣ f ∣ x) _ _)) ⟩
          ∣ g ∣ (∣ syn ∣ xs) ⊕ ((∣ f ∣ x ⊕ ∣ g ∣ (∣ syn ∣ ys)) ⊕ ∣ f ∣ y)
        ≈⟨ ⊕-cong X.refl (⊕-cong (⊕-comm (∣ f ∣ x) _) X.refl) ⟩
          ∣ g ∣ (∣ syn ∣ xs) ⊕ ((∣ g ∣ (∣ syn ∣ ys) ⊕ ∣ f ∣ x) ⊕ ∣ f ∣ y)
        ≈⟨ ⊕-cong X.refl (⊕-assoc _ (∣ f ∣ x) _) ⟩
          ∣ g ∣ (∣ syn ∣ xs) ⊕ (∣ g ∣ (∣ syn ∣ ys) ⊕ (∣ f ∣ x ⊕ ∣ f ∣ y))
        ≈⟨ X.sym (⊕-assoc _ _ _) ⟩
          (∣ g ∣ (∣ syn ∣ xs) ⊕ ∣ g ∣ (∣ syn ∣ ys)) ⊕ (∣ f ∣ x ⊕ ∣ f ∣ y)
        ≈⟨ ⊕-cong (∣ g ⊙ syn ∣-hom • (xs ∷ ys ∷ [])) (∣ f ∣-hom • (x ∷ y ∷ [])) ⟩
          ∣ g ∣ (∣ syn ∣ (xs ⊗ ys)) ⊕ ∣ f ∣ (x · y)
        ∎

      ∣resid∣⃗ : ∥ Frex ∥/≈ ↝ ∥ X ∥/≈
      ∣resid∣⃗ = record { ∣_∣      = ∣resid∣
                        ; ∣_∣-cong = ∣resid∣-cong
                        }

      _[_,_] : ∥ Frex ∥ₐ ⟿ ∥ X ∥ₐ
      _[_,_] = record { ∣_∣⃗    = ∣resid∣⃗
                      ; ∣_∣-hom = ∣resid∣-hom
                      }

  module _ {b ℓ} {X : Model {b} {ℓ}} where

    private

      open module X = Setoid ∥ X ∥/≈ renaming (_≈_ to _~_)

      _⊕_ : ∥ X ∥ → ∥ X ∥ → ∥ X ∥
      x ⊕ y = X ⟦ • ⟧ (x ∷ y ∷ [])

      ⊕-cong : ∀ {x y z w} → x ~ y → z ~ w → x ⊕ z ~ y ⊕ w
      ⊕-cong p q = (X ⟦ • ⟧-cong) (p ∷ q ∷ [])

      ⊕-comm : ∀ (x y : ∥ X ∥) → x ⊕ y ~ y ⊕ x
      ⊕-comm x y = ∥ X ∥ₐ-models comm (env {A = ∥ X ∥ₐ} (x ∷ y ∷ []))

      ⊕-assoc : ∀ (x y z : ∥ X ∥) → (x ⊕ y) ⊕ z ~ x ⊕ (y ⊕ z)
      ⊕-assoc x y z = ∥ X ∥ₐ-models assoc (env {A = ∥ X ∥ₐ} (x ∷ y ∷ z ∷ []))

    module _
      {f : ∥ A ∥ₐ ⟿ ∥ X ∥ₐ}
      {g : ∥ J n ∥ₐ ⟿ ∥ X ∥ₐ}
      where

      commute₁ : X [ f , g ] ⊙ inl ≗ f
      commute₁ = X.refl

      commute₂ : X [ f , g ] ⊙ inr ≗ g
      commute₂ {x} = ∣ g ∣-cong (syn⊙norm≗id {x = x})

      module _ {h : ∥ Frex ∥ₐ ⟿ ∥ X ∥ₐ}
        (c₁ : h ⊙ inl ≗ f)
        (c₂ : h ⊙ inr ≗ g)
        where

        open Reasoning ∥ X ∥/≈

        universal : X [ f , g ] ≗ h
        universal {sta x}     = X.sym c₁
        universal {dyn xs}    = begin
            ∣ g ∣ (∣ syn ∣ xs)
          ≈⟨ X.sym c₂ ⟩
            ∣ h ∣ (dyn (∣ norm ∣ (∣ syn ∣ xs)))
          ≈⟨ ∣ h ∣-cong (dyn norm⊙syn≗id) ⟩
            ∣ h ∣ (dyn xs)
          ∎
        universal {blob xs x} = begin
            ∣ g ∣ (∣ syn ∣ xs) ⊕ ∣ f ∣ x
          ≈⟨ ⊕-cong universal universal ⟩
            ∣ h ∣ (dyn xs) ⊕ ∣ h ∣ (sta x)
          ≈⟨ ∣ h ∣-hom • (dyn xs ∷ sta x ∷ []) ⟩
            ∣ h ∣ (blob xs x)
          ∎

CSemigroupFrex : FreeExtension
CSemigroupFrex = record { _[_]        = Frex
                        ; _[_]-isFrex = isFrex
                        }
  where isFrex : IsFreeExtension Frex
        isFrex A n =
          record { inl       = inl A n
                 ; inr       = inr A n
                 ; _[_,_]    = _[_,_] A n
                 ; commute₁  = λ {_ _ X f g} → commute₁ A n {X = X} {f} {g}
                 ; commute₂  = λ {_ _ X f g} → commute₂ A n {X = X} {f} {g}
                 ; universal = λ {_ _ X f g h} → universal A n {X = X} {f} {g} {h}
                 }