Zulip Chat Archive

Stream: lean4

Topic: type mismatch with identical types on named pattern match


Andrés Goens (Jul 01 2022 at 14:43):

I'm getting a strange behavior where Lean complains about a type mismatch, yet reports the expected and inferred types to be identical. This happens when trying to name a pattern-matched expression. Here is an attempt at an MWE:

inductive ListTree (α : Type) [BEq α] : List α  Type
 | leaf (val : List α) : ListTree α val
 | parentNil  (val : List α) : ListTree α val
 | parentCons (_ : ListTree α child) (_ : ListTree α sibling)
 (_ :  child.head? == sibling.head?) : ListTree α sibling

def ListTree.foo [BEq α] {l₁ l₂: List α} (h : l₁.head? == l₂.head?) : ListTree α l₁  ListTree α l₂  Option (ListTree α l₂)
  | t1@(leaf l₁), t₂@(parentNil l₂) => some $ parentCons t1 t2 h
  | _, _ => none

I get:

application type mismatch
  namedPattern t1 (leaf l₁)
argument
  leaf l₁
has type
  ListTree α l₁ : Type
but is expected to have type
  ListTree α l₁ : Type

Am I missing something here?

Sébastien Michelland (Jul 01 2022 at 14:53):

The latest nightly reveals the problem (notice the extra dagger):

application type mismatch
  namedPattern t1 (leaf l₁)
argument
  leaf l₁
has type
  ListTree α l₁ : Type
but is expected to have type
  ListTree α l₁ : Type

Pattern matching can destructure your inputs and name the subterms, but it can't implicitly check for equality. The pattern you wrote allows t₁ to be any leaf and then names that leaf's parameter l₁, shadowing the original name. By that time the strong information you had from the dependent type (that the parameter of leaf must be l₁) is lost.

The easy way around this problem is to simply let Lean infer with dependent matching.

def ListTree.foo [BEq α] {l₁ l₂: List α} (h : l₁.head? == l₂.head?) : ListTree α l₁  ListTree α l₂  Option (ListTree α l₂)
  | t₁@(leaf _), t₂@(parentNil _) => some $ parentCons t₁ t₂ h
  | _, _ => none

Mauricio Collares (Jul 01 2022 at 14:54):

(deleted, lost the race)

Andrés Goens (Jul 01 2022 at 15:05):

thanks for the explanation @Sébastien Michelland ! Does it mean I couldn't pattern match for the argument to be equal (without the @t name)? Can you pattern match to check for that equality? In my actual code I also care about that argument, just didn't need it for the MWE

Andrés Goens (Jul 01 2022 at 15:06):

btw, I thought I tried using the latest nightly, apparently changing it in the lean_toolchain file and restarting lean4 mode doesn't update the version :sweat_smile:

Sébastien Michelland (Jul 01 2022 at 15:10):

Barring more complicated features that I am not aware of, pattern matching is really only destructuring + binding new names. The thing is, checking for equality is not always possible; you either need the terms to be definitionally equal, or you need to have DecidableEq on the relevant type. Currently you only have BEq, which is simply any relation on the type, so you do not have the guarantees needed to compare List α. In addition to that, supporting equality would make it much harder to checking that patterns are exhaustive.

If you need to check for equality, then you would (1) want a strong enough structure on α: DecidableEq would definitely do, I believe there might also be something like LawfulBEq; and (2) check for equality explicitly.

Sébastien Michelland (Jul 01 2022 at 15:11):

As for changing the lean version, I personally run elan toolchain install leanprover/lean4:nightly-2022-07-01, then update the lean-toolchain file, and use the "Lean 4: Select Lean Toolchain" command of VSCode to make sure I switched to the new one.

Sebastian Ullrich (Jul 01 2022 at 15:21):

@Andrés Goens You likely want M-x lsp-workspace-restart in Emacs

Andrés Goens (Jul 01 2022 at 15:40):

ahh, right! just because someone says BEq corresponds to equality doesn't mean it actually does, that makes a lot of sense, thanks! Does that mean the delaborator checks for a DecidableEq instance in the pattern match and change its behavior accordingly? :open_mouth:

Sébastien Michelland (Jul 01 2022 at 15:47):

The pattern match remains the same and will shadow existing variables even if decidable equality is available. If you do want equality, you need to check after =>:

def follow_each_other (n m: Int): Bool :=
  match m with
--| .S n => true -- INCORRECT
  | .S n' => n' = n
  | _ => false

Andrés Goens (Jul 01 2022 at 19:06):

Yep, I've used that pattern too, it makes sense. Thanks for the explanation.

Andrés Goens (Jul 01 2022 at 19:07):

Sebastian Ullrich said:

Andrés Goens You likely want M-x lsp-workspace-restart in Emacs

Hm, that's curious, I actually don't get the dagger in emacs (I opened up vs code to check and it shows in vs code)

Andrés Goens (Jul 01 2022 at 19:09):

which is strange, I imagine that should come directly from the LSP, right?


Last updated: Dec 20 2023 at 11:08 UTC