Zulip Chat Archive

Stream: lean4

Topic: proving DecidableEq for inductives


Sébastien Michelland (May 06 2022 at 09:26):

I'm trying to provide DecidableEq for a nested/mutual inductive type by hand. Here is a fairly representative MWE:

inductive X :=
  | X1
  | X2
  | X3
  | Pair: (X × X)  X

def X.deq (a: X) (b: X): Decidable (a = b) :=
  match a, b with
  | X1, X1 => isTrue rfl
  | X2, X2 => isTrue rfl
  | X3, X3 => isTrue rfl
  | Pair (a₁,a₂), Pair (b₁,b₂) =>
      match deq a₁ b₁, deq a₂ b₂ with
      | isTrue h₁, isTrue h₂ => isTrue (by rw [h₁,h₂])
      | isFalse h₁, _ => isFalse (fun h => by cases h; cases (h₁ rfl))
      | _, isFalse h₂ => isFalse (fun h => by cases h; cases (h₂ rfl))
  | _, _ => isFalse sorry

The problem "on the surface" is that wildcard pattern does not provide enough information to prove the isFalse case. I'd like to write the function without listing all pairs of constructors, as my real example has quite a lot. Ideally I'd like to define a boolean equality first and then prove that it decides equality in a lemma. How should I go about that?

I've tried some variations on the matching (eg. match on the first argument then nest another match for the second) or using tactic mode to discharge the quadratic number of cases quickly, but none of my attempts feel really convincing.

Evgeniy Kuznetsov (May 06 2022 at 14:48):

inductive X :=
  | X1
  | X2
  | X3
  | Pair: (X × X)  X

def X.deq (a: X) (b: X): Decidable (a = b) := by
  generalize ha: a = a'
  generalize hb: b = b'
  cases a' <;> cases b' <;> try exact isFalse X.noConfusion
  case X1 => exact isTrue rfl
  case X2 => exact isTrue rfl
  case X3 => exact isTrue rfl
  case Pair a b =>
    let (a₁, a₂) := a
    let (b₁, b₂) := b
    exact match deq a₁ b₁, deq a₂ b₂ with
    | isTrue h₁, isTrue h₂ => isTrue (by rw [h₁,h₂])
    | isFalse h₁, _ => isFalse (fun h => by cases h; cases (h₁ rfl))
    | _, isFalse h₂ => isFalse (fun h => by cases h; cases (h₂ rfl))
decreasing_by
  subst ha hb
  simp_wf
  simp_arith

Sébastien Michelland (May 06 2022 at 15:46):

Thanks! This seems like a great start. I just had to change the let-binding on leanprover/lean4:nightly-2022-04-26 to uses cases (it didn't substitute a and b in the goal). I'll have to see if I can generalize it to my actual example now.

François G. Dorais (May 06 2022 at 17:45):

Changing the definition of Pair a bit makes it automatic:

inductive X :=
  | X1
  | X2
  | X3
  | Pair' : X  X  X
deriving DecidableEq

#synth DecidableEq X -- fun a b => instDecidableEqX a b

@[matchPattern]
abbrev X.Pair : X × X  X
| (x₁,x₂) => Pair' x₁ x₂

The matchPattern attribute allows you to use X.Pair in match expressions as if it was an actual constructor.

Sébastien Michelland (May 06 2022 at 17:48):

Yes I'm using deriving for most types, it's really handy. The real occurrence of nesting is with a list; there's some mutual definitions involved too, which makes it advanced enough that I wouldn't try to play with the Lean core either.

I didn't know about @[matchPattern] though, and I have a use for it right now, so thanks a lot for mentioning it.

Leonardo de Moura (May 06 2022 at 18:28):

When we implemented deriving for DecidableEq, we didn't have good support for well-founded recursion. We now have it, and we will eventually add support for nested and mutual inductive types at Lean/Elab/Deriving/DecEq.lean. It is not very high priority right now because Mathlib does not need it.

Leonardo de Moura (May 07 2022 at 00:17):

I pushed support for casesOn applications to the structural and well-founded recursion modules. This is useful for using recursion and cases tactic. For example, we don't need the generalizations steps above nor the decreasing_by anymore. It should work with any tactic that creates casesOn applications (e.g., rcases).
https://github.com/leanprover/lean4/blob/8c23bef39907e2599fb8ade149b7aa68ab4da311/tests/lean/run/casesRec.lean#L35-L49


Last updated: Dec 20 2023 at 11:08 UTC