Do-it-yourself Module Systems

Unbundling on the fly and mechanically obtaining termtypes from theories


Agda Implementors Meeting 2020


Musa Al-hassy
⟪ Flattened View ; Press ? for Help ⟫

Overview

With a bit of reflection, we can obtain

  1. a uniform, and practical, syntax for both records (semantics) and termtypes (syntax)
  2. on-the-fly unbundling; and,
  3. mechanically obtain data structures from theories

    ‘theory’ τ ‘data structure’ termtype τ
    pointed set 𝟙
    dynamic system ℕ
    monoid tree skeletons
    collections lists
    graphs (homogeneous) pairs
    actions infinite streams

Unbundling Problem

Monoid —“Untyped Composition”

  • We have things, called Carrier,
  • … and we want to ‘combine’ them via some operation _⨾_
  • … which has a ‘do nothing’ value Id.

Typeclass approach, as in Haskell

record MonoidOn (Carrier : Set) : Set
 where
  field
    _⨾_ : Carrier → Carrier → Carrier
    Id  : Carrier
    leftId : ∀ x → Id ⨾ x ≡ x
    rightId : ∀ x → x ⨾ Id ≡ x
    assoc   : ∀ x y z →  (x ⨾ y) ⨾ z
                       ≡  x ⨾ (y ⨾ z)
open MonoidOn {{...}}

Examples

  • Programs and sequential (or parallel) composition
  • Numbers and addition (or multiplication)
  • Writing words on a page —i.e., lists and catenation

What is “the” monoid on the natural numbers?

Additive?

instance
  ℕ₊ : MonoidOn ℕ
  ℕ₊ = record { _⨾_     = _+_
              ; Id      = 0
              ; leftId  = +-identityˡ
              ; rightId = +-identityʳ
              ;  assoc  = +-assoc }
ex : ∀ (m n : ℕ) → m ⨾ n  ≡  n ⨾ m
ex = +-comm

⇨ __⨾__ is determined to be _+_by instance search ⇦

likewise : ∀ (m : ℕ) → m ⨾ Id ≡ m
likewise = rightId

… or Multiplicative?

instance
  ℕₓ : MonoidOn ℕ
  ℕₓ = record { _⨾_     = _*_
              ; Id      = 1
              ; leftId  = *-identityˡ
              ; rightId = *-identityʳ
              ; assoc   = *-assoc }
whoops : ∀ {m : ℕ} → m ⨾ Id ≡ m
whoops = {!!}

⇨ ℕ₊ and ℕₓ are both candidates! No unique solution! ⇦

Haskell's Solution

Make two isomorphic copies of numbers …

Some types can be viewed as a monoid in more than one way, e.g. both addition and multiplication on numbers. In such cases we often define newtypes and make those instances of Monoid, e.g. Sum and Product. ---Hackage Data.Monoid

Sum α ≅ α  {- and -} Product α ≅ α

For Num α they have different monoid instances.

Unbundling in Agda

Start with fully bundled Monoid

then expose fields as parameters on the fly.

How?

Reflection!

  • Unfortunately, current mechanism cannot touch record-s directly.
  • But every record is a Σ-type (•̀ᴗ•́)و

Records as ΠΣ-types

  • Instead of the nice syntactic sugar

    record R (ε¹ : τ¹) ⋯ (εʷ : τʷ) : Set
      where
        field
          εʷ⁺¹ : τʷ⁺¹
          ⋮
          εʷ⁺ᵏ : τʷ⁺ᵏ
    
  • Use a more raw form ---eek!

    R  ≅  Π ε¹ : τ¹ • ⋯ • Π εʷ : τʷ
        • Σ εʷ⁺¹ : τʷ⁺¹ • ⋯ • Σ εʷ⁺ᵏ : τʷ⁺ᵏ
        • 𝟙
    

Terminology

R  ≅  Π ε¹ : τ¹ • ⋯ • Π εʷ : τʷ
    • Σ εʷ⁺¹ : τʷ⁺¹ • ⋯ • Σ εʷ⁺ᵏ : τʷ⁺ᵏ
    • 𝟙

⇨ “R is a ΠʷΣ type” —a (partitioned) context

⇨ It has parameters ε⁰, …, εʷ and fields εʷ⁺¹, …, εʷ⁺ᵏ

⇨ \(w\) is the “waist”

E.g., MonoidOn is a Π¹Σ type

Shucks, the Unbundling Problem Strikes Again

If we encode a record as a ΠʷΣ type, what if we want to instantiate, fix, a field —instead of a parameter?

We thus need a way to lift parameters to fields!

( Teaser: Π→λ ! )

Pragmatic Notation for Contexts

Instead of Set, use (waist : ℕ) → Set

  1. “Contexts” are exposure-indexed types

    Context = λ ℓ → ℕ → Set ℓ
    
  2. The “empty context” is the unit type

    End : ∀ {ℓ} → Context ℓ
    End {ℓ} = ‵ 𝟙 {ℓ}
    
  3. do-notation!

    _>>=_ : ∀ {a b}
          → (Γ : Context a)
          → (∀ {n} → Γ n → Context b)
          → Context (a ⊍ b)
    (Γ >>= f) zero    = Σ γ ∶ Γ 0 • f γ 0
    (Γ >>= f) (suc n) = Π γ ∶ Γ n • f γ n
    
  4. The “DIY” lies at >>=, permitting Σ, Π, 𝒲, let, … !

Example Context —Monoids

Monoid : Context ℓ₁
Monoid = do Carrier ← Set
            _⨾_     ← (Carrier → Carrier → Carrier)
            Id      ← Carrier
            leftId  ← ∀ (x : Carrier) → x ⨾ Id ≡ x
            rightId ← ∀ (x : Carrier) → Id ⨾ x ≡ x
            assoc   ← ∀ (x y z) → (x ⨾ y) ⨾ z  ≡  x ⨾ (y ⨾ z)
            End {ℓ}

Example Instance —Additive Naturals

ℕ₊′ : (Monoid ℓ₀ :waist 1) ℕ
ℕ₊′ = ⟨ _+_           -- _⨾_
      , 0             -- Id
      , +-identityˡ
      , +-identityʳ
      , +-assoc
      ⟩

Notice…

(Monoid ℓ₀ :waist 1) ℕ  ≅  MonoidOn ℕ

Π→λ —or _:waist_

 
record
≈
Context
≈
exposure-indexed type

Using   Contexts —‘reification’

  • If C : Context ℓ₀ then C w : Πʷ ⋯ • Set, but we want to apply C w to w-many parameters?

    (Πʷ x • τ) is w-many Π's binding variables to be used in type τ.

  • So we need a combinator…

     Π→λ  “Πʷ x • τ”   =   “λʷ x • τ”
    
  • with an infix form for contexts in particular …

    C :waist w   =   Π→λ (C w)
    

Example

Monoid : Context
Monoid = do C ← Set; _⨾_ : C → C → C; Id ← C; …

With no parameters, we have a Π⁰Σ-type

Monoid :waist 0  : Set₁
Monoid :waist 0  ≡  Σ C : Set • Σ _⨾_ : C → C → C • Σ Id : C • …

With one parameter, we regain MonoidOn

Monoid :waist 1  :  Π C : Set • Set
Monoid :waist 1  =  λ C : Set • Σ _⨾_ : C → C → C • Σ Id : C • …

With two parameters, we have a ‘solution’ to the additive-or-multiplicative-monoid-problem!

Monoid :waist 2  :  Π C : Set) • Π _⨾_ : C → C → C • Set
Monoid :waist 2  =  λ C : Set • λ _⨾_ : C → C → C • Σ Id : C • …

Relationships between various forms

Monoid : Context -- i.e., (w : ℕ) → Set

Monoid :waist 𝓌   :  Πʷ ⋯ • Set
Monoid :waist 𝓌   =  λʷ “parameters” • “fields”

11 Line Implementation —thanks Ulf!

The Core Syntactic Transformation

--  Π a : A • B a   ↦   λ a : A • B a

Π→λ-helper : Term → Term

Π→λ-helper (pi a (abs x b))
  = lam visible (abs x (Π→λ-helper b))

Π→λ-helper x
  = x

Keeping Track of Types

Term denotes untyped λ-terms, so let's keep track of the types when we convert Πs to λs.

Π→λ-type : Term → Term

Π→λ-type (pi a (abs x b))
 = pi a  (abs x (Π→λ-type b))

Π→λ-type x
 = unknown

Putting them together

macro
  Π→λ : Term → Term → TC Unit.⊤
  Π→λ τ goal
   =  normalise τ
       >>=ₜₑᵣₘ λ τ′ → checkType goal (Π→λ-type τ′)
       >>=ₜₑᵣₘ λ _ →  unify goal (Π→λ-helper τ′)

Lesson Learned

On-the-fly unbundling can be implemented as an in-language library in a dependently-typed language with sufficient reflection capabilities (•̀ᴗ•́)و

GADTs are Contexts too!

 
Monoid
⟿
do C ← Set; _⨾_ : C → C → C; Id : C; …
⟿
λ C : Set • Σ _⨾_ : C → C → C • Σ Id : C • …
⟿
λ C : Set • Σ _⨾_ : C → C → C • Σ Id : C • 𝟙
⟿
λ C : Set • C × C ⊍ C ⊍ 𝟙
⟿
μ C : Set • C × C ⊍ C ⊍ 𝟙

As a macro!

termtype : UnaryFunctor → Type
termtype τ = Fix (Σ→⊎ (sources τ))

macro
  termtype : Term → Term → TC Unit.⊤
  termtype tm goal =
                normalise tm
           >>=ₜₑᵣₘ λ tm′ → unify goal (def (quote Fix) ((vArg (Σ→⊎₀ (sourcesₜₑᵣₘ tm′))) ∷ []))

Monoids give rise to tree skeletons

Context

Monoid : ∀ ℓ → Context (ℓsuc ℓ)
Monoid ℓ = do Carrier ← Set ℓ
              _⨾_     ← (Carrier → Carrier → Carrier)
              Id      ← Carrier
              leftId  ← ∀ {x : Carrier} → Id ⨾ x ≡ x
              rightId ← ∀ {x : Carrier} → x ⨾ Id ≡ x
              assoc   ← ∀ {x y z} → (x ⨾ y) ⨾ z  ≡  x ⨾ (y ⨾ z)
              End {ℓ}

Termtype

  𝕄 : Set
  𝕄 = termtype (Monoid ℓ₀ :waist 1)
  that-is : 𝕄
          ≡ Fix (λ X →
                -- _⊕_, branch
                X × X × 𝟙
                -- Id, nil leaf
              ⊎ 𝟙
                -- invariant leftId
              ⊎ 𝟘
                -- invariant rightId
              ⊎ 𝟘
                -- invariant assoc
              ⊎ 𝟘
                --  the “End {ℓ}”
              ⊎ 𝟘)
  that-is = refl

Pattern synonyms for more compact presentation

-- : 𝕄
pattern emptyM
    = μ (inj₂ (inj₁ tt))
-- : 𝕄 → 𝕄 → 𝕄
pattern branchM l r
    = μ (inj₁ (l , r , tt))
-- absurd 𝟘-values
pattern absurdM a
    = μ (inj₂ (inj₂ (inj₂ (inj₂ a))))

termtype Monoid ≅ TreeSkeleton

  data TreeSkeleton : Set where
    empty  : TreeSkeleton
    branch : TreeSkeleton → TreeSkeleton → TreeSkeleton
  • “doing nothing”

      to : 𝕄 → TreeSkeleton
      to emptyM        = empty
      to (branchM l r) = branch (to l) (to r)
      to (absurdM (inj₁ ()))
      to (absurdM (inj₂ ()))
    
  • “doing nothing”

      from : TreeSkeleton → 𝕄
      from empty        = emptyM
      from (branch l r) = branchM (from l) (from r)
    

Dynamical Systems give rise to ℕ

Context

DynamicSystem : Context ℓ₁
DynamicSystem = do State ← Set
                   start ← State
                   next  ← (State → State)
                   End {ℓ₀}

Termtype

𝔻 = termtype (DynamicSystem :waist 1)

Pattern synonyms for more compact presentation

-- : 𝔻
pattern startD
    = μ (inj₁ tt)

-- : 𝔻 → 𝔻
pattern nextD e = μ (inj₂ (inj₁ e))

termtype 𝔻 ≅ ℕ

    to : 𝔻 → ℕ
    to startD    = 0
    to (nextD x) = suc (to x)

    from : ℕ → 𝔻
    from zero    = startD
    from (suc n) = nextD (from n)

Pointed Sets give rise to terminal types

PSet  : Context (ℓsuc ℓ₀)
PSet  = do Carrier ← Set ℓ₀
           point  ← Carrier
           End {ℓ₀}
ℙ𝕊𝕖𝕥 : Set
ℙ𝕊𝕖𝕥 = termtype (PSet :waist 1)
to : ℙ𝕊𝕖𝕥 → 𝟙 {ℓ₀}
to emptyM = tt

from : 𝟙 {ℓ₀} → ℙ𝕊𝕖𝕥
from _ = μ (inj₁ tt)

(Simple) Graphs give rise to pairs

Graph  : Context (ℓsuc ℓ₀)
Graph = do Node ← Set
           Edge ← Set
           adjacency ← (Node → Node → Edge)
           End {ℓ₀}
𝔾𝕣𝕒𝕡𝕙 : Set → Set
𝔾𝕣𝕒𝕡𝕙 X = termtype ((Graph :waist 2) X)
pattern _⇌_ x y = μ (inj₁ (x , y , tt))

view : ∀ {X} → 𝔾𝕣𝕒𝕡𝕙 X → X × X
view (x ⇌ y) = x , y

Indexed unary algebras (“actions”) give rise to streams

Context

Action  : Context ℓ₁
Action  = do Value    ← Set
             Program  ← Set
             run      ← (Program → Value → Value)
             End {ℓ₀}

Termtype

𝔸𝕔𝕥𝕚𝕠𝕟 : Set → Set
𝔸𝕔𝕥𝕚𝕠𝕟 X = termtype ((Action :waist 2) X)
-- : X → 𝔸𝕔𝕥𝕚𝕠𝕟 X → 𝔸𝕔𝕥𝕚𝕠𝕟 X
pattern _∷_ head tail
         = μ (inj₁ (tail , head , tt))

… to stream

record Stream (X : Set) : Set   where
  coinductive
  field
    hd : X
    tl : Stream X

open Stream
view : ∀ {I} → 𝔸𝕔𝕥𝕚𝕠𝕟 I → Stream I
hd (view (h ∷ t)) = h
tl (view (h ∷ t)) = view t

Collection theories give rise to lists

Collection : ∀ ℓ → Context (ℓsuc ℓ)
Collection ℓ = do Elem      ← Set ℓ
                  Container ← Set ℓ
                  insert    ← (Elem → Container → Container)
                  ∅         ← Container
                  End {ℓ}
ℂ : Set → Set
ℂ Elem = termtype ((Collection ℓ₀ :waist 2) Elem)
-- : X → ℂ X → ℂ X
pattern _::_ x xs
         = μ (inj₁ (x , xs , tt))

-- : ℂ X
pattern  ∅
    = μ (inj₂ (inj₁ tt))

Summary

‘theory’ τ ‘data structure’ termtype τ
pointed set 𝟙
dynamic system ℕ
monoid tree skeletons
collections lists
graphs (homogeneous) pairs
actions infinite streams

Many more theories τ to explore and see what data structures arise!

Bye!

Thanks for listening in!

These slides:

https://alhassy.github.io/next-700-module-systems/diy/agda-implementors-meeting-2020