3

This question is about

  • how to help Agda get unstuck when solving unification problems, and
  • how to convince Agda to solve a "heterogeneous constraint" (whatever that means)

The complete code for my question can be found here. I'll lay out my code and eventually get to the question. My project concerns proving things in Data.AVL, so I start with some boilerplate for that:

open import Data.Product
open import Level
open import Relation.Binary
open import Relation.Binary.PropositionalEquality as P using (_≡_)

module Data.AVL.Properties-Refuse
  {k v ℓ}
  {Key : Set k} (Value : Key → Set v)
  {_<_ : Rel Key ℓ}
  (isStrictTotalOrder : IsStrictTotalOrder P._≡_ _<_) where

  open import Data.AVL Value isStrictTotalOrder using (KV; module Extended-key; module Height-invariants; module Indexed)
  import Data.AVL Value isStrictTotalOrder as AVL
  open Extended-key                       
  open Height-invariants                  
  open Indexed                            

  open IsStrictTotalOrder isStrictTotalOrder

I then borrow an idea from Vitus to represent membership:

  data _∈_ {lb ub} (K : Key) : ∀ {n} → Tree lb ub n → Set (k ⊔ v ⊔ ℓ) where
    here : ∀ {hˡ hʳ} V
      {l : Tree lb [ K ] hˡ}
      {r : Tree [ K ] ub hʳ}
      {b : ∃ λ h → hˡ ∼ hʳ ⊔ h} →
      K ∈ node (K , V) l r (proj₂ b)
    left : ∀ {hˡ hʳ K′} {V : Value K′}
      {l : Tree lb [ K′ ] hˡ}
      {r : Tree [ K′ ] ub hʳ}
      {b : ∃ λ h → hˡ ∼ hʳ ⊔ h} →
      K < K′ →
      K ∈ l →
      K ∈ node (K′ , V) l r (proj₂ b)
    right : ∀ {hˡ hʳ K′} {V : Value K′}
      {l : Tree lb [ K′ ] hˡ}
      {r : Tree [ K′ ] ub hʳ}
      {b : ∃ λ h → hˡ ∼ hʳ ⊔ h} →
      K′ < K →
      K ∈ r →
      K ∈ node (K′ , V) l r (proj₂ b)

I then declare a function (whose meaning is irrelevant -- this is a contrived and simple version of a more meaningful function not shown here):

  refuse1 : ∀ {kₗ kᵤ h}
            ( t : Tree kₗ kᵤ h )
            ( k : Key )
            ( k∈t : k ∈ t ) →
            Set
  refuse1 = {!!}

So far, so good. Now, I case split on the default variables:

  refuse2 : ∀ {kₗ kᵤ h}
            ( t : Tree kₗ kᵤ h )
            ( k : Key )
            ( k∈t : k ∈ t ) →
            Set
  refuse2 t k₁ k∈t = {!!}

And now I split on t:

  refuse3 : ∀ {kₗ kᵤ h}
            ( t : Tree kₗ kᵤ h )
            ( k : Key )
            ( k∈t : k ∈ t ) →
            Set
  refuse3 (leaf l<u) k₁ k∈t = {!!}
  refuse3 (node k₁ t t₁ bal) k₂ k∈t = {!!}

And now on bal:

  refuse4 : ∀ {kₗ kᵤ h}
            ( t : Tree kₗ kᵤ h )
            ( k : Key )
            ( k∈t : k ∈ t ) →
            Set
  refuse4 (leaf l<u) k₁ k∈t = {!!}
  refuse4 (node k₁ t t₁ ∼+) k₂ k∈t = {!!}
  refuse4 (node k₁ t t₁ ∼0) k₂ k∈t = {!!}
  refuse4 (node k₁ t t₁ ∼-) k₂ k∈t = {!!}

The first error comes when I try to case split on k∈t of the equation including (node ... ∼+):

  refuse5 : ∀ {kₗ kᵤ h}
            ( t : Tree kₗ kᵤ h )
            ( k : Key )
            ( k∈t : k ∈ t ) →
            Set
  refuse5 (leaf l<u) k₁ k∈t = {!!}
  refuse5 (node k₁ t t₁ ∼+) k₂ k∈t = {!k∈t!}
  {- ERROR
    I'm not sure if there should be a case for the constructor here,
    because I get stuck when trying to solve the following unification
    problems (inferred index ≟ expected index):
      {_} ≟ {_}
      node (k₅ , V) l r (proj₂ b) ≟ node k₄
                                    t₂ t₃ ∼+
    when checking that the expression ? has type Set
  -}
  refuse5 (node k₁ t t₁ ∼0) k₂ k∈t = {!!}
  refuse5 (node k₁ t t₁ ∼-) k₂ k∈t = {!!}

Agda tells me it gets stuck doing the unification, but it's not clear to me why or how to help. In a response to a similar question, Ulf suggested first case-splitting on another variable. So, now working by hand, I focus on case-splitting the ∼+ line from refuse5 and include many of the implicit variables:

  open import Data.Nat.Base as ℕ

  refuse6 : ∀ {kₗ kᵤ h}
            ( t : Tree kₗ kᵤ h )
            ( k : Key )
            ( k∈t : k ∈ t ) →
            Set
  refuse6 {h = ℕ.suc .hˡ}
          (node (k , V) lk ku (∼+ {n = hˡ}))
          .k
          (here {hˡ = .hˡ} {hʳ = ℕ.suc .hˡ} .V {l = .lk} {r = .ku} {b = (ℕ.suc .hˡ , ∼+ {n = .hˡ})}) = {!!}
  {- ERROR
    Refuse to solve heterogeneous constraint proj₂ b :
    n ∼ hʳ ⊔ proj₁ b =?=
    ∼+ : n ∼ ℕ.suc n ⊔ ℕ.suc n
    when checking that the pattern
    here {hˡ = .hˡ} {hʳ = ℕ.suc .hˡ} .V {l = .lk} {r = .ku}
      {b = ℕ.suc .hˡ , ∼+ {n = .hˡ}}
    has type
    k₂ ∈ node (k₁ , V) lk ku ∼+
  -}
  refuse6 _ _ _ = ?

Oops. Now Agda goes from complaining that it's stuck to downright refusing to solve. What's going on here? Is it possible to specify the lhs of the equations with at least as much detail as in refuse5 and also case split on k∈t? If so, how?

Community
  • 1
  • 1
m0davis
  • 308
  • 1
  • 8
  • 1
    Shouldn't it be `here : ∀ {hˡ hʳ h} V {l : Tree lb [ K ] hˡ} {r : Tree [ K ] ub hʳ} {b : hˡ ∼ hʳ ⊔ h} → K ∈ node (K , V) l r b`? And similar for `left` and `right`. I guess your existential quantification introduces some mismatch. – effectfully Aug 15 '15 at 10:52
  • @user3237465 Yes, it turns out you are right. But why is that? I would have thought that the two definitions of `∈` are, for all intents and purposes, equivalent. – m0davis Aug 15 '15 at 17:57
  • @user3237465 A partial answer to the follow-up question I just posed, from [andrea on the agda list](http://article.gmane.org/gmane.comp.lang.agda/7841): "When unification gets stuck during pattern matching you have to change the expressions in the indexes to be more unifiable by turning more of them into variables, especially those that are not patterns themselves." – m0davis Aug 15 '15 at 18:11
  • Here's a link to the [discussion](http://thread.gmane.org/gmane.comp.lang.agda/7840/focus=7841) on the agda mailing list. – m0davis Aug 15 '15 at 18:41

1 Answers1

4

As mentioned in comments and on Agda mailing list, the solution is to replace with :

  data _∈_ {lb ub} (K : Key) : ∀ {n} → Tree lb ub n → Set (k ⊔ v ⊔ ℓ) where
    here : ∀ {hˡ hʳ h} V
      {l : Tree lb [ K ] hˡ}
      {r : Tree [ K ] ub hʳ}
      {b : hˡ ∼ hʳ ⊔ h} →
      K ∈ node (K , V) l r b
    ...

But you can also write

  data _∈_ {lb ub} (K : Key) : ∀ {n} → Tree lb ub n → Set (k ⊔ v ⊔ ℓ) where
    here : ∀ {hˡ hʳ} V
      {l : Tree lb [ K ] hˡ}
      {r : Tree [ K ] ub hʳ}
      {b : ∃ λ h → hˡ ∼ hʳ ⊔ h} {h' b'} →
      h' ≡ proj₁ b →
      b' ≅ proj₂ b →
      K ∈ node {h = h'} (K , V) l r b'
    ...

This is the usual technique to beat the green slime, which is the cause of the problem, I guess. We need heterogeneous equality in b' ≅ proj₂ b, because otherwise Agda would infer, that h is proj₁ b, and this proj₁ would make Agda unhappy.

The presence of ‘green slime’ — defined functions in the return types of constructors — is a danger sign.

There are at least two other techniques to beat the green slime, but they are too complicated for this case. You can found some discussion here. I don't know if using record projections instead of just functions should help unification (probably yes, since if we have proj₁ p == x and proj₂ p == y like in your case, then p == x , y and there is no ambiguity), but in general case unification must stuck and it's not a defect. See this for some explanations.

Community
  • 1
  • 1
effectfully
  • 12,325
  • 2
  • 17
  • 40
  • And, as answered [here](http://article.gmane.org/gmane.comp.lang.agda/7845), if one doesn't want to change the definition of `_∈_`, then define a new `_∈'_` like one of those above and then a view from one datatype to the other, x ∈ S -> x ∈' S. Then, use `with` to pattern match on the ∈' datatype. – m0davis Aug 16 '15 at 00:33
  • @m0davis, a view is a bit more complicated thing. I think, Andrea Vezzosi meant, that you can define a datatype, that is indexed by inhabitants of `_∈_`. It's "a view into". This allows to eliminate the using of projections just like when `∃` is replaced with `∀`, but you will have both `_∈_` and the view, which is cumbersome. I guess you can implement some form of unification constraints (like `b' ≅ proj₂ b`) handling via views as well, which is cumbersome too, but can probably be useful, when it's not possible to remove the green slime from a definition. – effectfully Aug 16 '15 at 02:24