Search code examples
with-statementagdaunification

Why agda with-abstraction don't erase some clauses?


I'm trying to write a theorem left as exercise in the Dependently Typed Programming in Agda tutorial written by Ulf Norell and James Chapman (page 23) and I don't understand a probably very simple thing about with.

Essentially I have these definitions

data   False : Set where
record True  : Set where

data Bool : Set where
  true  : Bool
  false : Bool

isTrue : Bool -> Set
isTrue true  = True
isTrue false = False

satisfies : {A : Set} -> (A -> Bool) -> A -> Set
satisfies p x = isTrue (p x)

data List (A : Set) : Set where
  []   : List A
  _::_ : (x : A)(xs : List A) -> List A

infixr 30 _:all:_
data All {A : Set} (P : A -> Set) : List A -> Set where
  all[]   : All P []
  _:all:_ : forall {x xs} -> P x -> All P xs -> All P (x :: xs)

filter : {A : Set} -> (A -> Bool) -> List A -> List A
filter p [] = []
filter p (x :: xs) with p x
... | true  = x :: filter p xs
... | false = filter p xs

and I'm trying to solve this

filter-all-lem : ∀ {p xs} -> All (satisfies p) (filter p xs)
filter-all-lem {p} {[]} with filter p []
... | [] = all[]
... | f :: fs = ?
filter-all-lem {p} {x :: xs} = ?

On the second hole I haven't tried anything yet and probably that's not the better way to to this, but my question isn't this one.

filter p [] is the first case of filter. It expands to []. So why agda can't infer that the case f :: fs is not possible? Unification should solve this really easily. But if I try to remove the clause entirely or use the absurd pattern I receive errors because not all the possibilities are covered.

And if the unification rules of agda don't do this automatically, how can I force it to be aware of it? Maybe rewrite? (I don't know how to rewrite in agda by now)


Solution

  • The with filter p [] is not needed in this case, as you say, you can already deduce that filter p [] = []. So you can just write

    filter-all-lem {p} {[]} = all[]
    

    For understanding with clauses it helps to know how Agda expands these, which is as a local function, where the thing being matched on (filter p [] in this case) is a new parameter. So you get

    filter-all-lem {p} {[]} = helper {p} (filter p [])
      where
      helper :: ∀ {p} ys -> All (satisfies p) ys
      helper [] = all[]
      helper (f :: fs) = ?
    

    And inside the helper you actually lose the knowledge that the list is empty. There is the inspect idiom in the standard library to keep this knowledge around in the form of an equality, but you don't need that here.

    As a general rule for proving things about a function that uses with (such as filter (x :: xs)), you should have the exact same structure of with clauses in your proofs, or with clauses that include the things being matched on in dependent types.