3
votes

I'm using sized types, and have a substitution function for typed terms which termination-checks if I give a definition directly, but not if I factor it via (monadic) join and fmap.

{-# OPTIONS --sized-types #-}

module Subst where

   open import Size

To show the problem, it's enough to have unit and sums. I have data types of Trie and Term, and I use tries with a codomain of Term inside Term, as part of the elimination form for sums.

   data Type : Set where
      ???? : Type
      _+_ : Type → Type → Type

   postulate Cxt : Set → Set

   -- Every value in the trie is typed in a context obtained by extending Γ.
   data Trie (A : Cxt Type → Set) : {ι : Size} → Type → Cxt Type → Set where
      〈〉 : ∀ {ι Γ} → A Γ → Trie A {↑ ι} ???? Γ
     〔_,_〕 : ∀ {ι τ₁ τ₂ Γ} → 
              Trie A {ι} τ₁ Γ → Trie A {ι} τ₂ Γ → Trie A {↑ ι} (τ₁ + τ₂) Γ

   -- The elimination form for + types is a trie whose values are terms.
   data Term (A : Cxt Type → Type → Set) : {ι : Size} → 
             Cxt Type → Type → Set where
      var : ∀ {ι Γ τ} → A Γ τ → Term A {↑ ι} Γ τ
      inl : ∀ {ι Γ τ₁ τ₂} → Term A {ι} Γ τ₁ → Term A {↑ ι} Γ (τ₁ + τ₂)
      match_as_ : ∀ {ι Γ τ τ′} → Term A {ι} Γ τ → 
                  Trie (λ Γ → Term A {ι} Γ τ′) {ι} τ Γ  → Term A {↑ ι} Γ τ′

Now, if I define substitution into terms directly (mutually with substitution into tries with codomain Term), then the definition termination-checks, even without size indices.

   -- Define substitution directly.
   _*_ : ∀ {A Γ Γ′ τ} → (∀ {τ} → A Γ τ → Term A Γ′ τ) → Term A Γ τ → Term A Γ′ τ
   _*ᵀ_ : ∀ {A Γ Γ′ τ τ′} → (∀ {τ} → A Γ τ → Term A Γ′ τ) →
          Trie (λ Γ → Term A Γ τ) τ′ Γ → Trie (λ Γ → Term A Γ τ) τ′ Γ′

   θ * var x = θ x
   θ * inl e = inl (θ * e)
   θ * (match e as κ) = match (θ * e) as (θ *ᵀ κ)

   θ *ᵀ 〈〉 x = 〈〉 (θ * x)
   θ *ᵀ 〔 κ₁ , κ₂ 〕 = 〔 θ *ᵀ κ₁ , θ *ᵀ κ₂ 〕

However, Trie and Term are functors, so it's natural to want to define substitution in terms of <$> (fmap) and join, where the latter collapses a term of terms into a term. Note that <$> and its counterpart for tries is size-preserving, and indeed we need to use size indices which reflect this for Agda to be satisfied that it terminates.

   -- Size-preserving fmap.
   _<$>ᵀ_ : ∀ {ι ???? τ Γ Γ′} {A B : Cxt Type → Set ????} → 
            (A Γ → B Γ′) → Trie A {ι} τ Γ → Trie B {ι} τ Γ′
   f <$>ᵀ (〈〉 x) = 〈〉 (f x)
   f <$>ᵀ 〔 σ₁ , σ₂ 〕 = 〔 f <$>ᵀ σ₁ , f <$>ᵀ σ₂ 〕

   _<$>_ : ∀ {ι A B Γ Γ′ τ} → 
           (∀ {τ} → A Γ τ → B Γ′ τ) → Term A {ι} Γ τ → Term B {ι} Γ′ τ
   f <$> var x = var (f x)
   f <$> inl e = inl (f <$> e)
   f <$> match e as κ = match f <$> e as ((λ e → f <$> e) <$>ᵀ κ)

   -- Monadic multiplication.
   join : ∀ {A Γ τ} → Term (Term A) Γ τ → Term A Γ τ
   join (var e) = e
   join (inl e) = inl (join e)
   join (match e as κ) = match join e as (join <$>ᵀ κ)

   -- Now substitution is easily defined in terms of join and fmap.
   _*′_ : ∀ {A Γ Γ′ τ} → (∀ {τ} → A Γ τ → Term A Γ′ τ) → Term A Γ τ → Term A Γ′ τ
   θ *′ e = join (θ <$> e)

Unfortunately join does not termination-check, and I'm not sure it's possible to use size indices to convince Agda that it does. Intuitively, it's clear enough that if you have a tree with a maximum depth of ι, and you replace its leaves with trees with a maximum depth of ι', you obtain a tree with a maximum depth of (ι + ι'). But I don't think this is the kind of thing you can establish with sized types or whether it would even help if you could. Note that without the mutual recursion into tries via join <$>ᵀ, the function termination-checks fine.

Is there a way to have this mutually recursive version of join termination-check, or am I stuck with writing substitution explicitly?

1
You just need a more precise type for join : ∀ {ι A Γ τ} → Term (Term A) {ι} Γ τ → Term A Γ τ. (and, btw your fmap does not work: the definition of Trie is not universe polymorphic so you cannot have A and B outputting a Set 𝒂 rather than a Set).gallais
Oh, now I'm slightly embarrassed. I was sure I'd tried that, but looking back at my original code I can see that I'd written Term (Term A {ι}) {ι} Γ τ, which doesn't work. (It type-checks but doesn't termination-check, although I'm not entirely sure why in either case.) Thanks for mentioning the Set 𝒂 problem (copy-and-paste mistake); fixed now.Roly
(Feel free to bump your comment to answer, by the way. If you can shed some light on why my attempt with the two {ι} indices type-checked but didn't termination-check, that would be even more useful.)Roly

1 Answers

1
votes

Give join the following type:

join : ∀ {ι A Γ τ} → Term (Term A) {ι} Γ τ → Term A Γ τ

(from gallais' comment above).