Zulip Chat Archive

Stream: lean4

Topic: pattern matching with 'cases' and 'match'


Paul Chisholm (Sep 28 2022 at 09:22):

Consider the following nine proofs:

theorem case₁ (n : Nat) : n = 0  (m : Nat, n = m+1) := by
  cases n with
  | zero   => apply Or.inl rfl
  | succ x => apply Or.inr
              exact Exists.intro x rfl

theorem case₂ (n : Nat) : n = 0  (m : Nat, n = m+1) := by
  cases n with
  | Nat.zero   => apply Or.inl rfl
  | Nat.succ x => apply Or.inr
                  exact Exists.intro x rfl

theorem case₃ (n : Nat) : n = 0  (m : Nat, n = m+1) := by
  cases n with
  | 0   => apply Or.inl rfl
  | x+1 => apply Or.inr
           exact Exists.intro x rfl

theorem match₁ (n : Nat) : n = 0  (m : Nat, n = m+1) := by
  match n with
  | zero   => apply Or.inl (by simp)
  | succ x => apply Or.inr
              exact Exists.intro x rfl

theorem match₂ (n : Nat) : n = 0  (m : Nat, n = m+1) := by
  match n with
  | Nat.zero   => apply Or.inl (by simp)
  | Nat.succ x => apply Or.inr
                  exact Exists.intro x rfl

theorem match₃ (n : Nat) : n = 0  (m : Nat, n = m+1) := by
  match n with
  | 0   => apply Or.inl (by simp)
  | x+1 => apply Or.inr
           exact Exists.intro x rfl

theorem term₁ : (n : Nat)  n = 0  (m : Nat, n = m+1)
  | zero   => Or.inl rfl
  | succ x => Or.inr (Exists.intro x rfl)

theorem term₂ : (n : Nat)  n = 0  (m : Nat, n = m+1)
  | Nat.zero   => Or.inl rfl
  | Nat.succ x => Or.inr (Exists.intro x rfl)

theorem term₃ : (n : Nat)  n = 0  (m : Nat, n = m+1)
  | 0   => Or.inl rfl
  | x+1 => Or.inr (Exists.intro x rfl)

Of these, case₁, match₂, match₃, term₂ and term₃ are successful. case₂, case₃, match₁ and term₁ fail.

If I precede the proofs with open Nat then match₁ and term₁ succeed. I can see why this would be the case, by opening Nat you get access to the unqualified names. But if that is the case, why does case₁ succeed without open Nat.

Also, why can I pattern match on 0 and +1 in match₃ and term₃ but not in case₃.

Mario Carneiro (Sep 28 2022 at 10:39):

cases does not take a term, it takes an identifier followed by a list of names. The identifier refers to the bare constructor name (without the namespace) because it's actually coming from the name of one of the binders in the T.recOn recursor

Mario Carneiro (Sep 28 2022 at 10:40):

That's why you have to write zero, not Nat.zero or 0 (the latter is a syntax error because it's not an identifier, and the former just doesn't match the expected name which is unqualified zero).

Mario Carneiro (Sep 28 2022 at 10:41):

I agree that this syntax is a bit inconsistent with pattern matching elsewhere

Sebastian Ullrich (Sep 28 2022 at 10:46):

Mario Carneiro said:

I agree that this syntax is a bit inconsistent with pattern matching elsewhere

I had this discussion with @Leonardo de Moura quite early. Leo's stance is that cases should be internally consistent with and without using. I tend to agree with you that external consistency with regular pattern matching is more important, especially since using using is not all that common.

Sebastian Ullrich (Sep 28 2022 at 10:52):

Note that it gets more complicated if we also consider induction, which adds induction hypotheses on top of the constructor parameters, so it really isn't a regular pattern anymore. One proposal I had was to introduce the IH for a parameter x unhygienically as x.ih. But again this begs the question what it should look like with using. It could be resolved using a naming convention, e.g. if your custom recursor has parameters x and x_ih and only bind the former as y, the latter will be auto-introduced as y.ih. We didn't continue the discussion after that I believe though. If someone is motivated to resolve this, a comprehensive RFC would be great I'd say.

Paul Chisholm (Sep 28 2022 at 11:08):

Thanks for the info. That makes sense. For tactical proofs, from a style perspective is one of match or cases preferred over the other?

Sebastian Ullrich (Sep 28 2022 at 11:10):

Whichever fits your use case better. match can do nested pattern matching. cases can do case analysis without listing all constructors.

Reinier van der Gronden (Sep 28 2022 at 11:21):

One proposal I had was to introduce the IH for a parameter x unhygienically as x.ih

This is what I'm currently using in my poc of 'autocomplete cases/induction statements'. Why would that be unhygienic?

Sebastian Ullrich (Sep 28 2022 at 11:23):

If you can access a name that you didn't literally introduce as such, that's unhygienic according to the usual definition for macro systems

Sebastian Ullrich (Sep 28 2022 at 11:23):

But you're talking about synthesizing syntax, right? Then that doesn't apply.

Reinier van der Gronden (Sep 28 2022 at 11:25):

Yeah I was talking about syntax. While I'm still a little new, I would think that if you had

induction n with
| succ n n.ih => sorry

that you have literally introduced it right? Or did you mean what to name those hypotheses if you only use induction n as a tactic?

Sebastian Ullrich (Sep 28 2022 at 11:26):

Yes, if you insert that on the user's behalf, everything is fine

Sebastian Ullrich (Sep 28 2022 at 11:27):

The hygiene-bending implementation would be

induction n with
| n + 1 => apply n.ih; ...

Last updated: Dec 20 2023 at 11:08 UTC