Zulip Chat Archive

Stream: new members

Topic: Unexpected pattern matching behaviour


Julian Sutherland (Dec 03 2021 at 23:34):

I'm working with a complex type family:

....
inductive TType : Type
| BlockList : FTContext  finset Identifier  finset Identifier  TType
| CBlock : FTContext  finset Identifier  TType
| SwitchBody : FTContext  finset Identifier  TType
| CExpr : FTContext  finset Identifier    TType
| CStatement : FTContext  finset Identifier  finset Identifier  TType

open TType

inductive Yulℂ : TType  Type
| EmpCBlock :  (Γ : FTContext) (vars : finset Identifier), Yulℂ (BlockList Γ vars vars)
| SeqCBlock  :
   (Γ : FTContext) (vars : finset Identifier) (vars' : finset Identifier) (vars'' : finset Identifier),
    Yulℂ (CStatement Γ vars vars') -> Yulℂ (BlockList Γ vars' vars'')  Yulℂ (BlockList Γ vars vars'')

| NestedScope :  (Γ : FTContext) (vars inner_vars inner_vars' : finset Identifier),
    VarStore inner_vars  Yulℂ (BlockList Γ (inner_vars  vars) (inner_vars'  vars))  Yulℂ (CBlock Γ vars)

| CCase :  (Γ : FTContext) (vars : finset Identifier),
    Literal  Yulℂ (CBlock Γ vars)  Yulℂ (SwitchBody Γ vars)  Yulℂ (SwitchBody Γ vars)
| CDefault :  (Γ : FTContext) (vars : finset Identifier),
    Yulℂ (CBlock Γ vars)  Yulℂ (SwitchBody Γ vars)

| CFunctionCall :  (Γ : FTContext) (vars : finset Identifier)
                    (f_id : Identifier) (n : ) (m : ),
    (Γ f_id = some (n,m)) 
    (fin n  Yulℂ (CExpr Γ vars 1)) 
    Yulℂ (CExpr Γ vars m)
| CId :  (Γ : FTContext) (vars : finset Identifier) (id : Identifier), id  vars  Yulℂ (CExpr Γ vars 1)
| CLit :  (Γ : FTContext) (vars : finset Identifier), Literal  Yulℂ (CExpr Γ vars 1)
| Scope :  (Γ : FTContext) (vars_outer vars_inner vars_inner' : finset Identifier)
               (m : ) (ret_vars : fin m  Identifier),
    VarStore (vars_inner  (tofinset ret_vars)) 
    Yulℂ (CStatement Γ (vars_inner  (tofinset ret_vars)) (vars_inner'  (tofinset ret_vars))) 
    Yulℂ (CExpr Γ vars_outer m)

| CBlock :  (Γ : FTContext) (vars : finset Identifier), Yulℂ (CBlock Γ vars)  Yulℂ (CStatement Γ vars vars)
  -- Function definitions already parsed into FTContext.
| CVariableDeclarationAss :
     (Γ : FTContext) (vars : finset Identifier) (n : ) (new_vars : fin n -> Identifier),
      Yulℂ (CExpr Γ vars n)   Yulℂ (CStatement Γ vars (vars  (tofinset new_vars)))
| CVariableDeclaration :
     (Γ : FTContext) (vars : finset Identifier) (n : ) (new_vars : fin n -> Identifier),
      Yulℂ (CStatement Γ vars (vars  (tofinset new_vars)))
| CAssignment :  (Γ : FTContext) (vars : finset Identifier) (n : ) (ids : fin n -> Identifier),
    tofinset ids  vars  Yulℂ (CExpr Γ vars n)  Yulℂ (CStatement Γ vars vars)
| CIf :  (Γ : FTContext) (vars : finset Identifier),
    Yulℂ (CExpr Γ vars 1)  Yulℂ (CBlock Γ vars)  Yulℂ (CStatement Γ vars vars)
| CExpressionStatement :  (Γ : FTContext) (vars : finset Identifier),
    Yulℂ (CExpr Γ vars 0) -> Yulℂ (CStatement Γ vars vars)
| CSwitch :  (Γ : FTContext) (vars : finset Identifier),
    Yulℂ (CExpr Γ vars 1) 
    Yulℂ (SwitchBody Γ vars) 
    Yulℂ (CStatement Γ vars vars)
| CFor :  (Γ : FTContext) (vars vars' vars'' vars''' : finset Identifier),
    Yulℂ (BlockList Γ vars vars')  Yulℂ (CExpr Γ vars' 1) 
    Yulℂ (BlockList Γ vars' vars'')  Yulℂ (BlockList Γ vars'' vars''') 
    Yulℂ (CStatement Γ vars vars)
| CBreak :  (Γ : FTContext) (vars : finset Identifier), Yulℂ (CStatement Γ vars vars)
| CContinue :  (Γ : FTContext) (vars : finset Identifier), Yulℂ (CStatement Γ vars vars)
| CLeave :  (Γ : FTContext) (vars : finset Identifier), Yulℂ (CStatement Γ vars vars)
| ForCheckCond :  (Γ : FTContext) (vars : finset Identifier),
    Yulℂ (CExpr Γ vars 1)  Yulℂ (BlockList Γ vars vars) -> Yulℂ (CExpr Γ vars 1) ->
    Yulℂ (CStatement Γ vars vars)
| ForExecBody :  (Γ : FTContext) (vars : finset Identifier),
    Yulℂ (CExpr Γ vars 1)  Yulℂ (BlockList Γ vars vars) 
    Yulℂ (CStatement Γ vars vars) -> Yulℂ (CStatement Γ vars vars)
| Skip :  (Γ : FTContext) (vars : finset Identifier), Yulℂ (CStatement Γ vars vars)

I'm getting some rather confusing pattern matching behaviour that I do not know how to get around.
When I define the function is_evaluated:

def is_evaluated :  {t : TType}, Yulℂ t  Prop
| (BlockList _ _ _) (EmpCBlock _ _) := true
| (BlockList _ _ _) _               := false
| (CBlock _ _) (NestedScope _ _ _ _ _ blklst) := is_evaluated blklst
| (SwitchBody _ _) _ := false
| (CExpr _ _ _) (CLit _ _ _) := true
| (CExpr _ _ _) (CId _ _ _ _) := false
| (CExpr _ _ n) (Scope _ _ _ _ _ _ _ cstmnt) := (n  1)  is_skip cstmnt
| (CExpr _ _ _) _ := false
| (CStatement _ _ _) cstmnt := is_skip cstmnt
| (CStatement _ {val := _, nodup := _} {val := _, nodup := _}) _ := false

Lean gives me the error

non-exhaustive match, the following cases are missing:
is_evaluated (CStatement _ {val := _, nodup := _} {val := _, nodup := _}) _

Note that this pattern match is _exactly_ the last pattern match of this function. I'm not sure how to approach fixing this any advice is much appreciated :)

Huỳnh Trần Khanh (Dec 04 2021 at 00:42):

argh... I already told you in another thread, programming language semantics is incredibly messy to deal with :joy: hey but I have a trick!

Huỳnh Trần Khanh (Dec 04 2021 at 00:42):

enter tactic mode

Huỳnh Trần Khanh (Dec 04 2021 at 00:42):

instead of defining is_evaluated using pattern matching, try using := begin ... end instead

Huỳnh Trần Khanh (Dec 04 2021 at 00:43):

then, inside the begin ... end block, type intros then cases on a variable

Huỳnh Trần Khanh (Dec 04 2021 at 00:44):

that variable will split into multiple variants and you can deal with each of them individually

Huỳnh Trần Khanh (Dec 04 2021 at 00:44):

the equation compiler is sometimes weird lol

Huỳnh Trần Khanh (Dec 04 2021 at 00:46):

basically the rule of thumb is if the equation compiler does something weird, enter tactic mode, if tactic mode does something weird, use the equation compiler :joy:

Huỳnh Trần Khanh (Dec 04 2021 at 00:56):

if you have nowhere to go, use recursors directly I guess but I've never had to do that

Chris B (Dec 04 2021 at 09:20):

What does the error look like if you set set_option pp.all true?

Julian Sutherland (Dec 04 2021 at 20:35):

Thanks! I gave that a try and got:

non-exhaustive match, the following cases are missing:
is_evaluated (TType.CStatement _ (@finset.mk.{0} Identifier _ _) (@finset.mk.{0} Identifier _ _)) _

So I then tried:

def is_evaluated :  {t : TType}, Yulℂ t  Prop
| (BlockList _ _ _) (EmpCBlock _ _) := true
| (BlockList _ _ _) _               := false
| (CBlock _ _) (NestedScope _ _ _ _ _ blklst) := is_evaluated blklst
| (SwitchBody _ _) _ := false
| (CExpr _ _ _) (CLit _ _ _) := true
| (CExpr _ _ _) (CId _ _ _ _) := false
| (CExpr _ _ n) (Scope _ _ _ _ _ _ _ cstmnt) := (n  1)  is_skip cstmnt
| (CExpr _ _ _) _ := false
| (CStatement _ _ _) cstmnt := is_skip cstmnt
| is_evaluated (CStatement _ (finset.mk.{0} _ _) (finset.mk.{0} _ _)) _ :=
  false

But I then get ill-formed match/equation expression on the first line of the pattern match... However, I can't see why that is... (Note that I have insert open TType right after the definition of TType)

Chris B (Dec 04 2021 at 20:57):

The last case shouldn't have is_evaluated on the left.

Julian Sutherland (Dec 04 2021 at 21:17):

Apologies! Sorry I copy and pasted the match suggested by the error message without paying enough attention!
Now

def is_evaluated :  {t : TType}, Yulℂ t  Prop
| (BlockList _ _ _) (EmpCBlock _ _) := true
| (BlockList _ _ _) _               := false
| (CBlock _ _) (NestedScope _ _ _ _ _ blklst) := is_evaluated blklst
| (SwitchBody _ _) _ := false
| (CExpr _ _ _) (CLit _ _ _) := true
| (CExpr _ _ _) (CId _ _ _ _) := false
| (CExpr _ _ n) (Scope _ _ _ _ _ _ _ cstmnt) := (n  1)  is_skip cstmnt
| (CExpr _ _ _) _ := false
| (CStatement _ _ _) cstmnt := is_skip cstmnt
| (CStatement _ (finset.mk.{0} _ _) (finset.mk.{0} _ _)) _ := false

Gives me

non-exhaustive match, the following cases are missing:
is_evaluated (TType.CStatement _ (@finset.mk.{0} Identifier _ _) (@finset.mk.{0} Identifier _ _)) _

while also reporting

equation compiler error, equation #10 has not been used in the compilation (possible solution: delete equation)

on the last line of the match, so same situation as before.

Julian Sutherland (Dec 04 2021 at 21:18):

@Huỳnh Trần Khanh I gave that a try, while it works, the generated equations are extremely large and quite difficult to use practically in proofs it seems... Thanks :)

Chris B (Dec 04 2021 at 21:25):

It's hard to troubleshoot without the whole thing being available; assuming Identifier is actually being inferred in the one you're writing (you can try @finset.mk.{0} Identifier _ _), one other thing you can do it make the last arm a wildcard _ to get it to typecheck and try to observe the definition to see how it's different from what you were trying to write.

Julian Sutherland (Dec 04 2021 at 21:34):

Apologies, Identifier is simply a string :)

I tried

| (CStatement _ (@finset.mk.{0} Identifier _ _) (finset.mk.{0} _ _)) _ := false

which resulted in the same situation. However, I was surprised that when I initially did:

| (CStatement _ (@finset.mk.{0} Identifier _ _) (@finset.mk.{0} Identifier _ _)) _ := false

I got the same errors as before, but also:

invalid pattern, 'Identifier' already appeared in this pattern

Which indicates that Lean seems to be interpreting this as a binding rather than a pattern?

Chris B (Dec 04 2021 at 21:36):

Is Identifier in a namespace?

Julian Sutherland (Dec 04 2021 at 21:37):

It is yes, it's used a number of times in this file, including in the definition of the Yulℂ type family :)

Chris B (Dec 04 2021 at 21:40):

If the namespace isn't open in the segment in which you write is_evaluated (unsure based on the example) you'll have to address Identifier by its full name.

Julian Sutherland (Dec 04 2021 at 21:47):

This same issue occurs if I use the (equivalent) pattern:

| (CStatement _ (@finset.mk.{0} string _ _) (@finset.mk.{0} string _ _)) _ := false

and I imagine that string is in scope by default from core.

Julian Sutherland (Dec 04 2021 at 21:49):

and, in fact, also occurs if I move the definition of Identifier into this file (so that it is definitely in the current namespace).

Chris B (Dec 04 2021 at 21:58):

I took the pretty-printer's bait. You've stumped me; you might want to post the full code somewhere since it seems difficult to resolve by eyeballing it.

Julian Sutherland (Dec 04 2021 at 22:02):

So, the only other relevant code is the preamble of the file and the definition of is_skip:

import tactic.linarith

import data.finset.basic
import data.vector
import init.data.fin.ops
import init.data.list.basic

def UInt256_max :  := 2^256
def UInt256 := fin UInt256_max
def Literal := UInt256
def Identifier := string

lemma zero_lt_uint256_max : 0 < UInt256_max :=
  begin
    cases (nat.eq_zero_or_pos UInt256_max),
    exfalso,
    injection h,
  end

def literal_zero : Literal :=
  fin.mk 0 zero_lt_uint256_max

set_option class.instance_max_depth 100

def FTContext := Identifier  option ( × )
def empΓ : FTContext := λ_, none

def VarStore (vars : finset Identifier) := Π i : Identifier, (i  vars)  Literal
def empStore : VarStore 
  | i i_in_emp := absurd i_in_emp (finset.not_mem_empty i)

def tofinset' :  (n : ) (α : Type) [decidable_eq α], vector α n -> finset α
| 0 _ _ vec := 
| (nat.succ n) α hs_un vec :=
  let union := (@finset.has_union α hs_un).union
  in union (singleton (vec.head)) (@tofinset' n α hs_un (vec.tail))

def tofinset {n : } {α : Type} [α_eq_dec : decidable_eq α] (f : fin n  α) : finset α :=
      tofinset' n α (vector.of_fn f)

inductive TType : Type
| BlockList : FTContext  finset Identifier  finset Identifier  TType
| CBlock : FTContext  finset Identifier  TType
| SwitchBody : FTContext  finset Identifier  TType
| CExpr : FTContext  finset Identifier    TType
| CStatement : FTContext  finset Identifier  finset Identifier  TType

open TType

inductive Yulℂ : TType  Type
| EmpCBlock :  (Γ : FTContext) (vars : finset Identifier), Yulℂ (BlockList Γ vars vars)
| SeqCBlock  :
   (Γ : FTContext) (vars : finset Identifier) (vars' : finset Identifier) (vars'' : finset Identifier),
    Yulℂ (CStatement Γ vars vars') -> Yulℂ (BlockList Γ vars' vars'')  Yulℂ (BlockList Γ vars vars'')

| NestedScope :  (Γ : FTContext) (vars inner_vars inner_vars' : finset Identifier),
    VarStore inner_vars  Yulℂ (BlockList Γ (inner_vars  vars) (inner_vars'  vars))  Yulℂ (CBlock Γ vars)

| CCase :  (Γ : FTContext) (vars : finset Identifier),
    Literal  Yulℂ (CBlock Γ vars)  Yulℂ (SwitchBody Γ vars)  Yulℂ (SwitchBody Γ vars)
| CDefault :  (Γ : FTContext) (vars : finset Identifier),
    Yulℂ (CBlock Γ vars)  Yulℂ (SwitchBody Γ vars)

| CFunctionCall :  (Γ : FTContext) (vars : finset Identifier)
                    (f_id : Identifier) (n : ) (m : ),
    (Γ f_id = some (n,m)) 
    (fin n  Yulℂ (CExpr Γ vars 1)) 
    Yulℂ (CExpr Γ vars m)
| CId :  (Γ : FTContext) (vars : finset Identifier) (id : Identifier), id  vars  Yulℂ (CExpr Γ vars 1)
| CLit :  (Γ : FTContext) (vars : finset Identifier), Literal  Yulℂ (CExpr Γ vars 1)
| Scope :  (Γ : FTContext) (vars_outer vars_inner vars_inner' : finset Identifier)
               (m : ) (ret_vars : fin m  Identifier),
    VarStore (vars_inner  (tofinset ret_vars)) 
    Yulℂ (CStatement Γ (vars_inner  (tofinset ret_vars)) (vars_inner'  (tofinset ret_vars))) 
    Yulℂ (CExpr Γ vars_outer m)

| CBlock :  (Γ : FTContext) (vars : finset Identifier), Yulℂ (CBlock Γ vars)  Yulℂ (CStatement Γ vars vars)
  -- Function definitions already parsed into FTContext.
| CVariableDeclarationAss :
     (Γ : FTContext) (vars : finset Identifier) (n : ) (new_vars : fin n -> Identifier),
      Yulℂ (CExpr Γ vars n)   Yulℂ (CStatement Γ vars (vars  (tofinset new_vars)))
| CVariableDeclaration :
     (Γ : FTContext) (vars : finset Identifier) (n : ) (new_vars : fin n -> Identifier),
      Yulℂ (CStatement Γ vars (vars  (tofinset new_vars)))
| CAssignment :  (Γ : FTContext) (vars : finset Identifier) (n : ) (ids : fin n -> Identifier),
    tofinset ids  vars  Yulℂ (CExpr Γ vars n)  Yulℂ (CStatement Γ vars vars)
| CIf :  (Γ : FTContext) (vars : finset Identifier),
    Yulℂ (CExpr Γ vars 1)  Yulℂ (CBlock Γ vars)  Yulℂ (CStatement Γ vars vars)
| CExpressionStatement :  (Γ : FTContext) (vars : finset Identifier),
    Yulℂ (CExpr Γ vars 0) -> Yulℂ (CStatement Γ vars vars)
| CSwitch :  (Γ : FTContext) (vars : finset Identifier),
    Yulℂ (CExpr Γ vars 1) 
    Yulℂ (SwitchBody Γ vars) 
    Yulℂ (CStatement Γ vars vars)
| CFor :  (Γ : FTContext) (vars vars' vars'' vars''' : finset Identifier),
    Yulℂ (BlockList Γ vars vars')  Yulℂ (CExpr Γ vars' 1) 
    Yulℂ (BlockList Γ vars' vars'')  Yulℂ (BlockList Γ vars'' vars''') 
    Yulℂ (CStatement Γ vars vars)
| CBreak :  (Γ : FTContext) (vars : finset Identifier), Yulℂ (CStatement Γ vars vars)
| CContinue :  (Γ : FTContext) (vars : finset Identifier), Yulℂ (CStatement Γ vars vars)
| CLeave :  (Γ : FTContext) (vars : finset Identifier), Yulℂ (CStatement Γ vars vars)
| ForCheckCond :  (Γ : FTContext) (vars : finset Identifier),
    Yulℂ (CExpr Γ vars 1)  Yulℂ (BlockList Γ vars vars) -> Yulℂ (CExpr Γ vars 1) ->
    Yulℂ (CStatement Γ vars vars)
| ForExecBody :  (Γ : FTContext) (vars : finset Identifier),
    Yulℂ (CExpr Γ vars 1)  Yulℂ (BlockList Γ vars vars) 
    Yulℂ (CStatement Γ vars vars) -> Yulℂ (CStatement Γ vars vars)
| Skip :  (Γ : FTContext) (vars : finset Identifier), Yulℂ (CStatement Γ vars vars)
  -- ForCheckCond, ForExecbody and Skip not in Yul specification, added for small step semantics.

open Yulℂ

def is_skip :  {Γ : FTContext} {vars vars': finset Identifier}, Yulℂ (CStatement Γ vars vars') -> Prop
| _ _ _ (Skip _ _) := true
| _ _ _ (CBlock _ _ _) := false
| _ _ _ (CVariableDeclarationAss _ _ _ _ _) := false
| _ _ _ (CVariableDeclaration _ _ _ _) := false
| _ _ _ (CAssignment _ _ _ _ _ _) := false
| _ _ _ (CIf _ _ _ _) := false
| _ _ _ (CExpressionStatement _ _ _) := false
| _ _ _ (CSwitch _ _ _ _) := false
| _ _ _ (CFor _ _ _ _ _ _ _ _ _) := false
| _ _ _ (CBreak _ _) := false
| _ _ _ (CContinue _ _) := false
| _ _ _ (CLeave _ _) := false
| _ _ _ (ForCheckCond _ _ _ _ _) := false
| _ _ _ (ForExecBody _ _ _ _ _) := false

def is_evaluated :  {t : TType}, Yulℂ t  Prop
| (BlockList _ _ _) (EmpCBlock _ _) := true
| (BlockList _ _ _) _               := false
| (CBlock _ _) (NestedScope _ _ _ _ _ blklst) := is_evaluated blklst
| (SwitchBody _ _) _ := false
| (CExpr _ _ _) (CLit _ _ _) := true
| (CExpr _ _ _) (CId _ _ _ _) := false
| (CExpr _ _ n) (Scope _ _ _ _ _ _ _ cstmnt) := (n  1)  is_skip cstmnt
| (CExpr _ _ _) _ := false
| (CStatement _ _ _) cstmnt := is_skip cstmnt
| (CStatement _ (@finset.mk.{0} Identifier _ _) (@finset.mk.{0} Identifier _ _)) _ := false

Chris B (Dec 04 2021 at 22:39):

I'm getting a segfault on the recursive case, but this doesn't show any errors for me:

def is_evaluated :  {t : TType}, Yulℂ t  Prop
| (BlockList _ _ _) (EmpCBlock _ _) := true
| (BlockList _ _ _) _               := false
--| (CBlock _ _) (NestedScope _ _ _ _ _ blklst) := is_evaluated blklst
| (CBlock _ _) _ := true
| (SwitchBody _ _) _ := false
| (CExpr _ _ _) (CLit _ _ _) := true
| (CExpr _ _ _) (CId _ _ _ _) := false
| (CExpr _ _ n) (Scope _ _ _ _ _ _ _ cstmnt) := (n  1)  is_skip cstmnt
| (CExpr _ _ _) _ := false
| (CStatement _ _ _) cstmnt := is_skip cstmnt

The dependent match for the NestedScope case shouldn't work as written anyway since blklst isn't going to be the same as t. That said the segfault is a mystery. I'm on 3.24 which is old-ish, so it may be fixed in newer versions.

Julian Sutherland (Dec 04 2021 at 22:50):

I'm on 3.34 myself. Will try and see if this works with a more recent version of lean 3 :)

I'm not sure I follow you? The recursive call to is_evaluated is well-typed. As you point out, lean will infer a different value of t, the implicit argument, but I don't see why that would be a problem?

Chris B (Dec 04 2021 at 23:01):

The inference wasn't working in 3.24, I'm sure they just improved the dependent pattern matching in the interim. I'm trying it on 3.35.1 and getting the same error you are. I think this might just a bug in the dependent pattern matching that's somehow caused by the NestedScope case.

Julian Sutherland (Dec 04 2021 at 23:03):

You're right, it seems to be the case that this recursive case is causing the problem. I guess I can get around this by explicitly matching on the EmpCBlock constructor in the CBlock case. Will give it a try.

Re: this potential bug, I will try and generate a #mwe, would it be best to raise this as an issue on GitHub?

Julian Sutherland (Dec 04 2021 at 23:07):

Hmmm, no success, I tried

def is_evaluated :  {t : TType}, Yulℂ t  Prop
| (BlockList _ _ _) (EmpCBlock _ _) := true
| (BlockList _ _ _) _               := false
| (CBlock _ _) (NestedScope _ _ _ _ _ (EmpCBlock _ _)) := true
| (CBlock _ _) (NestedScope _ _ _ _ _ _) := false
| (SwitchBody _ _) _ := false
| (CExpr _ _ _) (CLit _ _ _) := true
| (CExpr _ _ _) (CId _ _ _ _) := false
| (CExpr _ _ n) (Scope _ _ _ _ _ _ _ cstmnt) := (n  1)  is_skip cstmnt
| (CExpr _ _ _) _ := false
| (CStatement _ _ _) cstmnt := is_skip cstmnt

and got

equation compiler failed (use 'set_option trace.eqn_compiler.elim_match true' for additional details)
nested exception message:
cases tactic failed, unsupported equality between type and constructor indices
(only equalities between constructors and/or variables are supported, try cases on the indices):
quot.lift
    (λ (a₁ : list Identifier),
       quotient.lift ((λ (l₁ l₂ : list Identifier), (l₁.union l₂)) a₁) _ t_ᾰ_1.val)
    _
    ᾰ_inner_vars'.val =
  quot.lift
    (λ (a₁ : list Identifier),
       quotient.lift ((λ (l₁ l₂ : list Identifier), (l₁.union l₂)) a₁) _ t_ᾰ_1.val)
    _
    ᾰ_inner_vars.val

I tried setting set_option trace.eqn_compiler.elim_match true and the resulting message is gigantic...

Chris B (Dec 04 2021 at 23:07):

Yeah, the leanprover-community lean3 repo still handles issues and PRs.

Julian Sutherland (Dec 04 2021 at 23:08):

Super, will give that a try! :)

Julian Sutherland (Dec 04 2021 at 23:14):

Was able to get it working by defining an auxiliary function:

def is_empcblock :
   {Γ : FTContext} {vars vars' : finset Identifier}, Yulℂ (BlockList Γ vars vars')  Prop
| _ _ _ (EmpCBlock _ _) := true
| _ _ _ _ := false

def is_evaluated :  {t : TType}, Yulℂ t  Prop
| (BlockList _ _ _) blklst := is_empcblock blklst
| (CBlock _ _) (NestedScope _ _ _ _ _ blklst) := is_empcblock blklst
| (SwitchBody _ _) _ := false
| (CExpr _ _ _) (CLit _ _ _) := true
| (CExpr _ _ _) (CId _ _ _ _) := false
| (CExpr _ _ n) (Scope _ _ _ _ _ _ _ cstmnt) := (n  1)  is_skip cstmnt
| (CExpr _ _ _) _ := false
| (CStatement _ _ _) cstmnt := is_skip cstmnt

Nonetheless, I will try and generate an #mwe as soon possible, as I see no reason the original should not have worked.

@Chris B Thanks so much for all your help :)

Huỳnh Trần Khanh (Dec 05 2021 at 04:10):

Julian Sutherland said:

Huỳnh Trần Khanh I gave that a try, while it works, the generated equations are extremely large and quite difficult to use practically in proofs it seems... Thanks :)

for the record, there is a way to make the generated equations easier to deal with. you should prove lemmas that correspond to the cases that you normally state while pattern matching and to prove these lemmas, you can use squeeze_simp and possibly some induction and for the most part, lean can simplify the humongous expression on its own. take a look at this tiny example:

import tactic

def sum_of_1_to_n :    := begin
  intro n,
  induction n,
  exact 0,
  exact n_n + n_ih,
end

lemma base_case : sum_of_1_to_n 0 = 0 := begin
  simp only [sum_of_1_to_n, nat.rec_zero], -- generated by squeeze_simp
end

lemma induction_hypothesis :  n, sum_of_1_to_n (n + 1) = sum_of_1_to_n n + n := begin
  intro n,
  induction n,
  simp only [sum_of_1_to_n, nat.rec_add_one], -- generated by squeeze_simp
  simp only [sum_of_1_to_n, nat.rec_add_one], -- generated by squeeze_simp
  ring,
end

squeeze_simp is quite helpful actually

Huỳnh Trần Khanh (Dec 05 2021 at 04:30):

and here's a nontrivial example

import tactic

inductive word_size_t
| char
| i32
| i64

inductive integer_expression_t : word_size_t  Type
| const { word_size : word_size_t } (value : ) : integer_expression_t word_size
| coerce { word_size word_size' : word_size_t } (a : integer_expression_t word_size') : integer_expression_t word_size
| load { word_size : word_size_t } (index : integer_expression_t word_size_t.i32) : integer_expression_t word_size
| add { word_size : word_size_t } (a b : integer_expression_t word_size) : integer_expression_t word_size
| subtract { word_size : word_size_t } (a b : integer_expression_t word_size) : integer_expression_t word_size
| multiply { word_size : word_size_t } (a b : integer_expression_t word_size) : integer_expression_t word_size
| divide { word_size : word_size_t } (a b : integer_expression_t word_size) : integer_expression_t word_size
| mod { word_size : word_size_t } (a b : integer_expression_t word_size) : integer_expression_t word_size
| bitwise_and { word_size : word_size_t } (a b : integer_expression_t word_size) : integer_expression_t word_size
| bitwise_or { word_size : word_size_t } (a b : integer_expression_t word_size) : integer_expression_t word_size
| bitwise_xor { word_size : word_size_t } (a b : integer_expression_t word_size) : integer_expression_t word_size
| bitwise_not { word_size : word_size_t } (a : integer_expression_t word_size) : integer_expression_t word_size

def evaluate :  { word_size : word_size_t }, integer_expression_t word_size   := begin
  intros _ x,
  induction x,
  exact 1,
  exact x_ih + 1,
  exact x_ih + 1,
  exact x_ih_a + x_ih_b + 1,
  exact x_ih_a + x_ih_b + 1,
  exact x_ih_a + x_ih_b + 1,
  exact x_ih_a + x_ih_b + 1,
  exact x_ih_a + x_ih_b + 1,
  exact x_ih_a + x_ih_b + 1,
  exact x_ih_a + x_ih_b + 1,
  exact x_ih_a + x_ih_b + 1,
  exact x_ih + 1,
end

lemma a1 :  word_size x, evaluate (@integer_expression_t.const word_size x) = 1 := begin
  intros,
  simp only [evaluate],
end

lemma a2 :  word_size word_size' x, evaluate (@integer_expression_t.coerce word_size word_size' x) = evaluate x + 1 := begin
  intros,
  induction x,
  repeat {
    simp only [evaluate],
  },
end

lemma a3 :  word_size x, evaluate (@integer_expression_t.load word_size x) = evaluate x + 1 := begin
  intros,
  simp only [evaluate],
end

lemma a4 :  word_size x y, evaluate (@integer_expression_t.add word_size x y) = evaluate x + evaluate y + 1 := begin
  intros,
  simp only [evaluate],
end

lemma a5 :  word_size x y, evaluate (@integer_expression_t.subtract word_size x y) = evaluate x + evaluate y + 1 := begin
  intros,
  simp only [evaluate],
end

lemma a6 :  word_size x y, evaluate (@integer_expression_t.multiply word_size x y) = evaluate x + evaluate y + 1 := begin
  intros,
  simp only [evaluate],
end

lemma a7 :  word_size x y, evaluate (@integer_expression_t.divide word_size x y) = evaluate x + evaluate y + 1 := begin
  intros,
  simp only [evaluate],
end

lemma a8 :  word_size x y, evaluate (@integer_expression_t.mod word_size x y) = evaluate x + evaluate y + 1 := begin
  intros,
  simp only [evaluate],
end

lemma a9 :  word_size x y, evaluate (@integer_expression_t.bitwise_and word_size x y) = evaluate x + evaluate y + 1 := begin
  intros,
  simp only [evaluate],
end

lemma a10 :  word_size x y, evaluate (@integer_expression_t.bitwise_or word_size x y) = evaluate x + evaluate y + 1 := begin
  intros,
  simp only [evaluate],
end

lemma a11 :  word_size x y, evaluate (@integer_expression_t.bitwise_xor word_size x y) = evaluate x + evaluate y + 1 := begin
  intros,
  simp only [evaluate],
end

lemma a12 :  word_size x, evaluate (@integer_expression_t.bitwise_not word_size x) = evaluate x + 1 := begin
  intros,
  simp only [evaluate],
end

Huỳnh Trần Khanh (Dec 05 2021 at 04:31):

you should do this whenever the equation compiler acts up


Last updated: Dec 20 2023 at 11:08 UTC