I'm having a problem with termination checking, very similar to the one described in this question and also this Agda bug report/feature request.
The problem is convincing the compiler that the following unionWith
terminates. Using a combining function for duplicate keys, unionWith
merges two maps represented as lists of (key, value) pairs sorted by key. The Key parameter of a finite map is a (non-tight) lower bound on the keys contained in the map. (One reason for defining this data type is to provide a semantic domain into which I can interpret AVL trees in order to prove various properties about them.)
open import Function
open import Relation.Binary renaming (IsEquivalence to IsEq)
open import Relation.Binary.PropositionalEquality as P using (_≡_)
module FiniteMap
{k v ℓ ℓ′}
{Key : Set k}
(Value : Set v)
{_<_ : Rel Key ℓ}
(isStrictTotalOrder : IsStrictTotalOrder _≡_ _<_)
{_≈_ : Rel Value ℓ′}
(isEquivalence : IsEq _≈_)
where
open import Algebra.FunctionProperties
open import Data.Product
open IsStrictTotalOrder isStrictTotalOrder
open import Level
KV : Set (k ⊔ v)
KV = Key × Value
data FiniteMap (l : Key) : Set (k ⊔ v ⊔ ℓ) where
[] : FiniteMap l
_∷_ : (kv : KV) → let k = proj₁ kv in l < k → (m : FiniteMap k) → FiniteMap l
unionWith : ∀ {l} → Op₂ Value → Op₂ (FiniteMap l)
unionWith _ [] [] = []
unionWith _ [] m = m
unionWith _ m [] = m
unionWith _⊕_ (_∷_ (k , v) k<l m) (_∷_ (k′ , v′) k′<l m′) with compare k k′
... | tri< k<k′ _ _ = _∷_ (k , v) k<l (unionWith _⊕_ m (_∷_ (k′ , v′) k<k′ m′))
... | tri≈ _ k≡k′ _ rewrite P.sym k≡k′ = {!!} --_∷_ (k , v ⊕ v′) k<l (unionWith _⊕_ m m′)
... | tri> _ _ k′<k = _∷_ (k′ , v′) k′<l (unionWith _⊕_ (_∷_ (k , v) k′<k m) m′)
I've been unable to generalise the solutions discussed in the referenced question to my setting. For example if I introduce an auxiliary function unionWith'
, defined mutually recursively with unionWith
, which is invoked from the latter in the k' < k
case:
unionWith : ∀ {l} → Op₂ Value → Op₂ (FiniteMap l)
unionWith′ : ∀ {l} → Op₂ Value → (kv : KV) → let k = proj₁ kv in l < k → FiniteMap k → Op₁ (FiniteMap l)
unionWith _ [] [] = []
unionWith _ [] m = m
unionWith _ m [] = m
unionWith _⊕_ (_∷_ (k , v) k<l m) (_∷_ (k′ , v′) k′<l m′) with compare k k′
... | tri< k<k′ _ _ = _∷_ (k , v) k<l (unionWith _⊕_ m (_∷_ (k′ , v′) k<k′ m′))
... | tri≈ _ k≡k′ _ rewrite P.sym k≡k′ = {!!} --_∷_ (k , v ⊕ v′) k<l (unionWith _⊕_ m m′)
... | tri> _ _ k′<k = _∷_ (k′ , v′) k′<l (unionWith′ _⊕_ (k , v) k′<k m m′)
unionWith′ _ (k , v) l<k m [] = _∷_ (k , v) l<k m
unionWith′ _⊕_ (k , v) l<k m (_∷_ (k′ , v′) k′<l m′) with compare k k′
... | tri< k<k′ _ _ = {!!}
... | tri≈ _ k≡k′ _ = {!!}
... | tri> _ _ k′<k = _∷_ (k′ , v′) k′<l (unionWith′ _⊕_ (k , v) k′<k m m′)
then as soon as I tie the recursive knot by replacing the first missing case in unionWith'
with the required call to unionWith
, it fails to termination-check.
I also tried introducing additional with
patterns, but that's complicated by need to use the result of compare
in the recursive calls. (If I use nested with
clauses that doesn't seem to help the termination checker.)
Is there a way to use with
patterns or auxiliary functions to get this compiling? It seems like a straightforward enough situation, so I'm hoping it just a case of knowing the right trick.
(Maybe the new termination checker in the Agda development branch can deal with this, but I'd like to avoid installing a development version unless I have to.)