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.)
unionWith′
) with{-# OPTIONS --termination-depth=2 #-}
at the top of the file? I think it should work, but I want to be sure before I write an answer. – VitusunionWith'
solution seems to work, without any explicit setting of--termination-depth
. I'll post that as an answer for future reference. Unfortunately, it's not easy for me to roll back to 2.3.2, so I can't conduct your--termination-depth=2
experiment. Sorry about that (especially as I said that I wanted to avoid installing a development version). – Roly--termination-depth
useless; it's now enabled "by default" (the termination checker works as if--termination-depth=infinity
, if I understood the release notes correctly), so my first instinct was to try explicitly setting--termination-depth
in earlier version. I had a development version from November, which I think doesn't have the new algorithm, and it seems to work there. – Vitus--termination-depth
in 2.3.2, but not set to 2. I did try some bigger numbers though (20) and it didn't help. I got briefly excited when I tried 1000 and it looked like everything was passing, before I realised the termination checker itself hadn't terminated. (Well, I guess it might have done eventually...) – Roly--termination-depth=20
should do everything--termination-depth=2
does and more, so the (older) version I tried already had more clever termination checker. Never mind. :) – Vitus