Zulip Chat Archive

Stream: lean4

Topic: deep match behind an Option a structure


Ryan Lahfa (May 05 2021 at 12:49):

I'm trying to match what a function does, which itself performs matches, but each time I have to match a mk due to the structure definition, or the context is not quite what I wished for:

inductive KrivineInstruction
| Access (n: Nat)
| Grab (next: KrivineInstruction)
| Push (next: KrivineInstruction) (continuation: KrivineInstruction)

inductive KrivineClosure
| pair (i: KrivineInstruction) (e: List KrivineClosure)

def KrivineEnv := List KrivineClosure

structure KrivineState where
  code: KrivineInstruction
  env: KrivineEnv
  stack: KrivineEnv

def evalKrivineMachine (state: KrivineState): Option KrivineState :=
match state.code, state.env, state.stack with
| KrivineInstruction.Access 0, (KrivineClosure.pair code recEnv :: closures), stack => some $ KrivineState.mk code recEnv stack
| KrivineInstruction.Access n, (KrivineClosure.pair code recEnv :: closures), stack => some $ KrivineState.mk (KrivineInstruction.Access (n - 1)) closures stack
| KrivineInstruction.Push c' c, env, stack => some $ KrivineState.mk c env (KrivineClosure.pair c' env :: stack)
| KrivineInstruction.Grab code, closures, (KrivineClosure.pair c₀ e₀ :: stack) => some $ KrivineState.mk code (KrivineClosure.pair c₀ e₀ :: closures) stack
| _, _, _ => none


def List.max: List Nat  Nat
| [] => 0
| x :: q => Nat.max x (max q)

partial def KrivineClosure.depthUnsafe: KrivineClosure -> Nat
| KrivineClosure.pair i env => 1 + List.max (List.map depthUnsafe env)

set_option codegen false in
@[implementedBy KrivineClosure.depthUnsafe]
def KrivineClosure.depth: KrivineClosure -> Nat :=
fun closure => by induction closure with
| pair i env depth_env => exact 1 + depth_env
| nil => exact 0
| cons head tail head_depth tail_depth => exact Nat.max head_depth tail_depth

namespace KrivineEnv
def depth: KrivineEnv -> Nat
| [] => 0
| closure :: closures => Nat.max (1 + KrivineClosure.depth closure) (depth closures)

def depth_rel: KrivineEnv -> KrivineEnv -> Prop := measure depth
def depth_wf: WellFounded (measure depth) := measureWf depth

def correct: KrivineEnv -> Prop :=
WellFounded.fix depth_wf (fun e correct => match e with
  | [] => true
  | KrivineClosure.pair c₀ e₀ :: env => C[List.length e₀](compile.leftInv c₀)  (correct e₀ (by admit))  (correct env (sorry))
)
end KrivineEnv

def KrivineState.correct (state: KrivineState): Prop :=
  C[List.length state.env](compile.leftInv state.code)
   (KrivineEnv.correct state.env)
   (KrivineEnv.correct state.stack)

-- here I want to perform case-analysis on (evalKrivineMachine state0) return.
theorem transitionCorrectness (state₀: KrivineState) (state₁: KrivineState)
  (hTransition: evalKrivineMachine state₀ = state₁): KrivineState.correct state₀ -> KrivineState.correct state₁ :=
by match state₀.code, state₀.env, state₀.stack with
| KrivineInstruction.Access 0, (KrivineClosure.pair code recEnv :: closures), stack => exact sorry
| KrivineInstruction.Access n, (KrivineClosure.pair code recEnv :: closures), stack => exact sorry
| KrivineInstruction.Push c' c, env, stack => exact sorry
| KrivineInstruction.Grab code, closures, (KrivineClosure.pair c₀ e₀ :: stack) => exact sorry
| _, _, _ => exact sorry

But, neither approach I use enable me to match properly the return of evalKrivineMachine, BTW, is it expected that evalKrivineMachine state_0 = state_1 is smart enough to know that it is in fact: evalKrivineMachine state_0 = some state_1 (which the context shows!) ?

Ryan Lahfa (May 06 2021 at 19:07):

Ryan Lahfa said:

I'm trying to match what a function does, which itself performs matches, but each time I have to match a mk due to the structure definition, or the context is not quite what I wished for:

inductive KrivineInstruction
| Access (n: Nat)
| Grab (next: KrivineInstruction)
| Push (next: KrivineInstruction) (continuation: KrivineInstruction)

inductive KrivineClosure
| pair (i: KrivineInstruction) (e: List KrivineClosure)

def KrivineEnv := List KrivineClosure

structure KrivineState where
  code: KrivineInstruction
  env: KrivineEnv
  stack: KrivineEnv

def evalKrivineMachine (state: KrivineState): Option KrivineState :=
match state.code, state.env, state.stack with
| KrivineInstruction.Access 0, (KrivineClosure.pair code recEnv :: closures), stack => some $ KrivineState.mk code recEnv stack
| KrivineInstruction.Access n, (KrivineClosure.pair code recEnv :: closures), stack => some $ KrivineState.mk (KrivineInstruction.Access (n - 1)) closures stack
| KrivineInstruction.Push c' c, env, stack => some $ KrivineState.mk c env (KrivineClosure.pair c' env :: stack)
| KrivineInstruction.Grab code, closures, (KrivineClosure.pair c₀ e₀ :: stack) => some $ KrivineState.mk code (KrivineClosure.pair c₀ e₀ :: closures) stack
| _, _, _ => none


def List.max: List Nat  Nat
| [] => 0
| x :: q => Nat.max x (max q)

partial def KrivineClosure.depthUnsafe: KrivineClosure -> Nat
| KrivineClosure.pair i env => 1 + List.max (List.map depthUnsafe env)

set_option codegen false in
@[implementedBy KrivineClosure.depthUnsafe]
def KrivineClosure.depth: KrivineClosure -> Nat :=
fun closure => by induction closure with
| pair i env depth_env => exact 1 + depth_env
| nil => exact 0
| cons head tail head_depth tail_depth => exact Nat.max head_depth tail_depth

namespace KrivineEnv
def depth: KrivineEnv -> Nat
| [] => 0
| closure :: closures => Nat.max (1 + KrivineClosure.depth closure) (depth closures)

def depth_rel: KrivineEnv -> KrivineEnv -> Prop := measure depth
def depth_wf: WellFounded (measure depth) := measureWf depth

def correct: KrivineEnv -> Prop :=
WellFounded.fix depth_wf (fun e correct => match e with
  | [] => true
  | KrivineClosure.pair c₀ e₀ :: env => (correct e₀ (by admit))  (correct env (sorry))
)
end KrivineEnv

def KrivineState.correct (state: KrivineState): Prop :=
   (KrivineEnv.correct state.env)
   (KrivineEnv.correct state.stack)

-- here I want to perform case-analysis on (evalKrivineMachine state0) return.
theorem transitionCorrectness (state₀: KrivineState) (state₁: KrivineState)
  (hTransition: evalKrivineMachine state₀ = state₁): KrivineState.correct state₀ -> KrivineState.correct state₁ :=
by match state₀.code, state₀.env, state₀.stack with
| KrivineInstruction.Access 0, (KrivineClosure.pair code recEnv :: closures), stack => exact sorry
| KrivineInstruction.Access n, (KrivineClosure.pair code recEnv :: closures), stack => exact sorry
| KrivineInstruction.Push c' c, env, stack => exact sorry
| KrivineInstruction.Grab code, closures, (KrivineClosure.pair c₀ e₀ :: stack) => exact sorry
| _, _, _ => exact sorry

But, neither approach I use enable me to match properly the return of evalKrivineMachine, BTW, is it expected that evalKrivineMachine state_0 = state_1 is smart enough to know that it is in fact: evalKrivineMachine state_0 = some state_1 (which the context shows!) ?

So to answer myself, I can match the mk constructor then rematch the variables.
Though, I'm still running into issues because it looks like Lean 4 is not able to eliminate an match after a simp, I'm getting this in my goal:

some function (match KrivineInstruction.Access 0, KrivineClosure.pair code recEnv :: closures, stack with
            | KrivineInstruction.Access 0, KrivineClosure.pair code recEnv :: closures, stack =>
              some { code := code, env := recEnv, stack := stack }
            | KrivineInstruction.Access n, KrivineClosure.pair code recEnv :: closures, stack =>
              some { code := KrivineInstruction.Access (n - 1), env := closures, stack := stack }
            | KrivineInstruction.Push c' c, env, stack =>
              some { code := c, env := env, stack := KrivineClosure.pair c' env :: stack }
            | KrivineInstruction.Grab code, closures, KrivineClosure.pair c₀ e₀ :: stack =>
              some { code := code, env := KrivineClosure.pair c₀ e₀ :: closures, stack := stack }
            | x, x_1, x_2 => none).code

And I think it can be simplified to some { code := code, env := recEnv, stack := stack } but I'm not sure how to debug this further.

Ryan Lahfa (May 09 2021 at 14:06):

Okay, I have a very simple repro:

inductive A
| A (n: Nat)
| B (n: A)

inductive C
| pair (i: A) (e: List C)

def D := List C

structure S where
  a: A
  b: D
  c: D

def eval (state: S): Option S :=
match state.a, state.b, state.c with
| A.A 0, (C.pair c subExpr :: tail), stack =>
  some $ S.mk c subExpr stack
| A.A (Nat.succ n), (C.pair c subExpr :: tail), stack =>
  some $ S.mk (A.A n) tail stack
| A.B c, env, stack =>
  some $ S.mk c env stack
| _, _, _ =>
  none

def test_0: eval { a := A.A 0, b := [C.pair (A.A 1) []], c := [] } = some { a := A.A 1, b := [], c := []} :=
by
simp [eval]

In my code, I would like to reduce the lhs into rhs, so simp [eval] turns into:

match A.A 0, [C.pair (A.A 1) []], [] with
    | A.A 0, C.pair c subExpr :: tail, stack => some { a := c, b := subExpr, c := stack }
    | A.A (Nat.succ n), C.pair c subExpr :: tail, stack => some { a := A.A n, b := tail, c := stack }
    | A.B c, env, stack => some { a := c, b := env, c := stack }
    | x, x_1, x_2 => none) =
    some { a := A.A 1, b := [], c := [] }

At any moment, this proof can be closed by rfl, but if I'm in a case where I would like to exploit the reduction, I would need to inject have: eval $a = $target := by rfl and use it. Is there any way to reuse simp here?

Ryan Lahfa (May 09 2021 at 14:09):

Ha, have is not working

Ryan Lahfa (May 09 2021 at 14:10):

This:

inductive A
| A (n: Nat)
| B (n: A)

inductive C
| pair (i: A) (e: List C)

def D := List C

structure S where
  a: A
  b: D
  c: D

def eval (state: S): Option S :=
match state.a, state.b, state.c with
| A.A 0, (C.pair c subExpr :: tail), stack =>
  some $ S.mk c subExpr stack
| A.A (Nat.succ n), (C.pair c subExpr :: tail), stack =>
  some $ S.mk (A.A n) tail stack
| A.B c, env, stack =>
  some $ S.mk c env stack
| _, _, _ =>
  none

def test_0: eval { a := A.A 0, b := [C.pair (A.A 1) []], c := [] } = some { a := A.A 1, b := [], c := []} :=
by
have eval { a := A.A 0, b := [C.pair (A.A 1) []], c := [] } = some $ { a := A.A 1, b := [], c := [] } := by rfl
exact this

Context:

unsolved goals
 ?m.1228 = ?m.1228

Ryan Lahfa (May 09 2021 at 14:40):

I don't understand how to debug this kind of issue as it looks like all unification are succeeding:

[Meta.isDefEq]
    [Meta.isDefEq.step] ?m.1157 =?= ?m.1157
    [Meta.isDefEq] ?m.1157 =?= ?m.1157 ... success
  [Meta.isDefEq]
    [Meta.isDefEq.step] eval { a := A.A 0, b := [C.pair (A.A 1) []] } =
      some { a := A.A 1, b := [] } =?= eval { a := A.A 0, b := [C.pair (A.A 1) []] } = some { a := A.A 1, b := [] }
    [Meta.isDefEq] eval { a := A.A 0, b := [C.pair (A.A 1) []] } =
      some
        { a := A.A 1,
          b := [] } =?= eval { a := A.A 0, b := [C.pair (A.A 1) []] } = some { a := A.A 1, b := [] } ... success
[Meta.isDefEq]
  [Meta.isDefEq.step] eval { a := A.A 0, b := [C.pair (A.A 1) []] } =
    some { a := A.A 1, b := [] } =?= eval { a := A.A 0, b := [C.pair (A.A 1) []] } = some { a := A.A 1, b := [] }
  [Meta.isDefEq] eval { a := A.A 0, b := [C.pair (A.A 1) []] } =
    some
      { a := A.A 1,
        b := [] } =?= eval { a := A.A 0, b := [C.pair (A.A 1) []] } = some { a := A.A 1, b := [] } ... success
invalid pattern
[Meta.isDefEq]
    [Meta.isDefEq.step] ?m.1157 =?= ?m.1164 = ?m.1164
    [Meta.isDefEq] ?m.1157 [assignable] =?= ?m.1164 = ?m.1164 [nonassignable]
      [Meta.isDefEq.assign]
        [Meta.isDefEq.assign] ?m.1157 := ?m.1164 = ?m.1164
        [Meta.isDefEq.assign.beforeMkLambda] ?m.1157 [] := ?m.1164 = ?m.1164
        [Meta.isDefEq.assign.checkTypes]
          [Meta.isDefEq.step] Sort ?u.1156 =?= Prop
          [Meta.isDefEq.assign.final] ?m.1157 := ?m.1164 = ?m.1164
    [Meta.isDefEq] ?m.1164 = ?m.1164 =?= ?m.1164 = ?m.1164 ... success
[Meta.isDefEq]
  [Meta.isDefEq.step] ?m.1164 = ?m.1164 =?= ?m.1164 = ?m.1164
  [Meta.isDefEq] ?m.1164 = ?m.1164 =?= ?m.1164 = ?m.1164 ... success

Ryan Lahfa (May 09 2021 at 14:44):

Elaborator traces seems to show:

[Elab.app.args] explicit: false, rfl :  {α : Sort ?u.1162} {a : α}, a = a
  [Elab.app.finalize] rfl
  [Elab.app.finalize] after etaArgs, rfl : ?m.1164 = ?m.1164
  [Elab.app.finalize] expected type: ?m.1157

So if I understand well, ?m.1164 is α ?

Mac (May 09 2021 at 16:08):

Shouldn't:

have eval { a := A.A 0, b := [C.pair (A.A 1) []] } = some { a := A.A 1, b := [] } := by rfl

be:

have : eval { a := A.A 0, b := [C.pair (A.A 1) []] } = some { a := A.A 1, b := [] } := by rfl

That is, with a colon (and maybe with a name for the hypothesis) -- or I am missing something? I would think that is why you are getting "invalid pattern".

Ryan Lahfa (May 09 2021 at 16:33):

Mac said:

Shouldn't:

have eval { a := A.A 0, b := [C.pair (A.A 1) []] } = some { a := A.A 1, b := [] } := by rfl

be:

have : eval { a := A.A 0, b := [C.pair (A.A 1) []] } = some { a := A.A 1, b := [] } := by rfl

That is, with a colon (and maybe with a name for the hypothesis) -- or I am missing something? I would think that is why you are getting "invalid pattern".

In Lean 4, it is not AFAIK.
Your addition creates expected identifier error

Mac (May 09 2021 at 17:59):

It might need to be named in Lean 4? That is:

have h : eval { a := A.A 0, b := [C.pair (A.A 1) []] } = some { a := A.A 1, b := [] } := by rfl

That's what I would expect from the "expected identifier" error.

Ryan Lahfa (May 09 2021 at 18:08):

Mac said:

It might need to be named in Lean 4? That is:

have h : eval { a := A.A 0, b := [C.pair (A.A 1) []] } = some { a := A.A 1, b := [] } := by rfl

That's what I would expect from the "expected identifier" error.

Surprisingly, it works, but I do not understand why the "no name" version fails as it has worked at some other points. Though, that indeed solves most of my issues.

I can also do have $type from $proof to have a this local decl.


Last updated: Dec 20 2023 at 11:08 UTC