Zulip Chat Archive

Stream: new members

Topic: Can we provide proofs to match expressions?


Jonathan Lacombe (Aug 22 2024 at 00:58):

Hi, I have been encountering multiple scenarios where a match expression tells me I am "missing cases", even when I know all cases are covered.

Is there a way to provide evidence that all cases are covered? Or if not, would writing a custom match using recursors be possible/feasible?

Example: at the bottom there is a match case with sorry, which should be an unreachable case.

def Vector (N: Nat) := { x: List Nat // x.length = N }

def Vector.mk (ns: List Nat) (h: ns.length = N := by simp): Vector N := Subtype.mk ns h

theorem Vector.of_empty_length (a: Vector N) (k: a.val = []): ([]: List Number).length = N := by
  have p := a.property
  rw [k] at p
  exact p

local macro "access" x:ident "." y:ident : term => `( $(x).$(y) )

local macro "vector_cons " a:ident ha:ident an:ident as:ident : tactic =>
  `(tactic|
    ( have z := List.length_cons $an $as
      have p := access $a . property
      rw [←$ha] at z
      rw [z] at p
      rw [p]
      simp))

theorem Vector.of_cons_length (a: Vector N) (ha : a.val = an :: as): N - 1 < N := by
  vector_cons a ha an as

theorem Vector.tail_minus_one (a: Vector N) (ha: a.val = an :: as): as.length = N - 1 := by
  vector_cons a ha an as

def Vector.add (a: Vector N) (b: Vector N): Vector N :=
  match ha: a.val, hb: b.val with
  | [],     []      =>
    Vector.mk [] (of_empty_length a ha)

  | an::as, bn::bs  =>
    have termination: N - 1 < N := Vector.of_cons_length a ha

    let vas: Vector (N - 1) := Vector.mk as (Vector.tail_minus_one a ha)
    let vbs: Vector (N - 1) := Vector.mk bs (Vector.tail_minus_one b hb)
    let sum: Vector (N - 1) := Vector.add vas vbs

    have proof: ((an + bn) :: sum.val).length = N := by
      simp
      have g := sum.property
      rw [sum.property]
      have t := termination
      rw [ g] at t
      replace t := Nat.not_eq_zero_of_lt t
      rw [Nat.sub_one_add_one]
      exact t

    Vector.mk ((an+bn)::sum.val) proof

  | _,      _       =>
    sorry

Chris Bailey (Aug 22 2024 at 02:39):

I don't think I understand the last one, what if one of the sides is empty and the other one isn't?

Jonathan Lacombe (Aug 22 2024 at 02:42):

Chris Bailey said:

I don't think I understand the last one, what if one of the sides is empty and the other one isn't?

Sorry I didn't explain. The Vector N is a subtype that restricts the length of the vector to size N (defined in first line), so they will always have the same size as defined by the function.

Jonathan Lacombe (Aug 22 2024 at 02:44):

It is impossible to call Vector.add (Vector.mk [1, 2]) (Vector.mk [1, 2, 3]) since the matching propositions wouldn't be provided

Jonathan Lacombe (Aug 22 2024 at 02:47):

I did find something interesting though. Just like the have statement I have for the termination proof, you can include have statements before the match to constrain the type.

I tested this by adding the following right before the match:

have restrict_cases: av.length = 0  bv.length = 0 := by
    sorry

With this I can remove the _, _ differing case but the other an::as, bn::bs case as well (which of course I don't want)

Unlike termination proofs. I do not see what goal I need to be able to prove and remove the cases where one side is empty and the other isn't. So right now, I am just guessing to see if they end up working

Jonathan Lacombe (Aug 22 2024 at 02:51):

It would be cool if whenever a case is missing from a match, lean could provide a goal to solve to handle that case. I think that could be big especially when programming with a lot of subtypes

Chris Bailey (Aug 22 2024 at 03:11):

I think there are some typos in the of_empty_length lemma?

Chris Bailey (Aug 22 2024 at 03:11):

You basically need to show why it's not reachable within the match arm:

def Vector (N: Nat) := { x: List Nat // x.length = N }

def Vector.mk (ns: List Nat) (h: ns.length = N := by simp): Vector N := Subtype.mk ns h

theorem Vector.of_empty_length (a: Vector N) (k: a.val = []): ([]: List Nat).length = N := by
  have p := a.property
  rw [k] at p
  exact p

local macro "access" x:ident "." y:ident : term => `( $(x).$(y) )

local macro "vector_cons " a:ident ha:ident an:ident as:ident : tactic =>
  `(tactic|
    ( have z := List.length_cons $an $as
      have p := access $a . property
      rw [←$ha] at z
      rw [z] at p
      rw [p]
      simp))

theorem Vector.of_cons_length (a: Vector N) (ha : a.val = an :: as): N - 1 < N := by
  vector_cons a ha an as

theorem Vector.tail_minus_one (a: Vector N) (ha: a.val = an :: as): as.length = N - 1 := by
  vector_cons a ha an as

def Vector.add (a: Vector N) (b: Vector N): Vector N :=
  match ha: a.val, hb: b.val with
  | [],     []      =>
    Vector.mk [] (of_empty_length a ha)

  | an::as, bn::bs  =>
    have termination: N - 1 < N := Vector.of_cons_length a ha

    let vas: Vector (N - 1) := Vector.mk as (Vector.tail_minus_one a ha)
    let vbs: Vector (N - 1) := Vector.mk bs (Vector.tail_minus_one b hb)
    let sum: Vector (N - 1) := Vector.add vas vbs

    have proof: ((an + bn) :: sum.val).length = N := by
      simp
      have g := sum.property
      rw [sum.property]
      have t := termination
      rw [ g] at t
      replace t := Nat.not_eq_zero_of_lt t
      rw [Nat.sub_one_add_one]
      exact t

    Vector.mk ((an+bn)::sum.val) proof
  | [], b'::bs => by
    have h0 := Vector.of_empty_length a ha
    have h1 := List.length_cons b' bs
    have h2 : N = (b' :: bs).length := hb  b.property.symm
    have h3 := List.length_nil  h0
    have h4 := h2  h1
    have h5 : 0 = bs.length + 1 := h3  h4
    cases h5
  | a'::as, [] =>
    have h0 := Vector.of_empty_length b hb
    have h1 := List.length_cons a' as
    have h2 : N = (a' :: as).length := ha  a.property.symm
    have h3 := List.length_nil  h0
    have h4 := h2  h1
    have h5 : 0 = as.length + 1 := h3  h4
    cases h5

Chris Bailey (Aug 22 2024 at 03:13):

The goal is to produce an element of the correct type, but for unreachable arms you usually proceed by showing that there's a contradiction present somewhere.

Jonathan Lacombe (Aug 22 2024 at 03:16):

Chris Bailey said:

The goal is to produce an element of the correct type, but for unreachable arms you usually proceed by showing that there's a contradiction present somewhere.

Thank you so much. There's always something that I just never heard/learned about. You saved me a lot of time I would have spent just staring at this.

Kyle Miller (Aug 22 2024 at 05:01):

In case it's useful, here are a few patterns you can find in the wild for dispatching impossible match arms:

  • ... => by exfalso; ...
  • ... => absurd ... ...
  • ... => False.elim (...)

The first and third are both for making use of a proof of False.


Last updated: May 02 2025 at 03:31 UTC