Zulip Chat Archive

Stream: general

Topic: lemma from match statement


Scott Viteri (Aug 05 2020 at 22:41):

Are there any lemmas that tie the matched variables to the particular case it was matched with?

I have a term constructor, and I am trying to define a function on it as follows:

inductive term : Type
| const : string  option sort  term
| app : term  term  term

notation `clause` := list (option term)
infixl `  ` :20  := app

@[simp] def reduce_not_and_aux : term  clause
| (const "and" _  t₀  (const "and" _  t₁  t₂)) :=
    mkNot t₀ :: mkNot t₁ :: reduce_not_and_aux t₂
| (const "and" _  t₀  t₁)  := [mkNot t₀, mkNot t₁]
| t := [mkNot t]

However the equation compiler fails, and when I try to set_option for additional details, emacs crashes.
I believe that lean is not inferring that t₂ is smaller than the full term. I have constructed a 'term_size' function, and I am trying to prove that it decreases.

This brings me to the original question -- can the match statement give me a lemma about what it matched?
Specifically I would like to say that if the reduce_not_and_aux takes a term t, then t =(const "and" _ • t₀ • (const "and" _ • t₁ • t₂)) in the first case.

Thank You,
Scott

Yakov Pechersky (Aug 06 2020 at 00:23):

A simp attribute works only on lemmas (and theorems), as far as I know.

Scott Viteri (Aug 06 2020 at 00:26):

woops thx

Scott Morrison (Aug 06 2020 at 01:22):

No, @[simp] works on definitions too.

Scott Morrison (Aug 06 2020 at 01:23):

It means that simp will happily unfold the definition itself. In my experience it's rarely what you want, and it's better to write custom simp lemmas yourself in nearly every circumstance.

Scott Morrison (Aug 06 2020 at 01:23):

The attribute @[simps] is extremely useful when defining structures: it will automatically create @[simp] lemmas for each of the projections.

Scott Viteri (Aug 10 2020 at 17:48):

I appreciate the comments here, but I would still be interested in an answer to the original question if possible.

Scott Viteri (Aug 10 2020 at 19:54):

Nevermind!

Mario Carneiro (Aug 10 2020 at 20:05):

Hm, there seems to be a weird bug here:

constant sort : Type

inductive term : Type
| const : string  option sort  term
| app : term  term  term
open term

def reduce_not_and_aux : term  unit
| (app _ t) := reduce_not_and_aux t
| t := ()

If you replace constant sort : Type with def sort : Type := sorry it works, which suggests somehow the noncomputable marker is interfering with the equation compiler

Mario Carneiro (Aug 10 2020 at 20:09):

But even after fixing that, it seems the equation compiler still has an issue with the definition, which I assume is what you are seeing. The problem has to do with the way string matches are compiled. Here's an implementation that will work:

def sort : Type := sorry

inductive term : Type
| const : string  option sort  term
| app : term  term  term
open term

notation `clause` := list (option term)
infixl `  ` :20  := app

def mkNot : term  term := sorry

def is_and : term  bool
| (const "and" _) := tt
| _ := ff

def reduce_not_and_aux : term  clause
| t@(A1  t₀  (A2  t₁  t₂)) :=
  if is_and A1  is_and A2 then
    mkNot t₀ :: mkNot t₁ :: reduce_not_and_aux t₂
  else [mkNot t]
| t@(A1  t₀  t₁) :=
  if is_and A1 then [mkNot t₀, mkNot t₁]
  else [mkNot t]
| t := [mkNot t]

Mario Carneiro (Aug 10 2020 at 20:10):

(it's about now that I wish lean had pattern guards)

Mario Carneiro (Aug 10 2020 at 20:13):

If you are doing meta code, then the original version will work fine:

meta def reduce_not_and_aux : term  clause
| (const "and" _  t₀  (const "and" _  t₁  t₂)) :=
  mkNot t₀ :: mkNot t₁ :: reduce_not_and_aux t₂
| (const "and" _  t₀  t₁) := [mkNot t₀, mkNot t₁]
| t := [mkNot t]

Scott Viteri (Aug 11 2020 at 19:53):

Thanks Mario!
I had written the full definitions using term.rec_on to unblock myself, but this is much nicer.

On a separate note, is there a way to pattern match using functions?
In other places I have definitions like

def reduce_iff_aux : term  nat  clause
| (const "iff" _  t₀  t₁) 0 := [mkNot t₀, t₁]
| (const "iff" _  t₀  t₁) 1 := [t₀, mkNot t₁]
| _           _ := [option.none]
 ```
But I would much rather define something like

def iff (t₁ t₂ : term) : term := const "iff" _ • t₁ • t₂
def reduce_iff_aux : term → nat → clause
| (iff • t₀ • t₁) 0 := [mkNot t₀, t₁]
| (iff • t₀ • t₁) 1 := [t₀, mkNot t₁]
| _ _ := [option.none]


Reid Barton (Aug 11 2020 at 19:55):

Try putting @[pattern] before def iff

Scott Viteri (Aug 11 2020 at 19:56):

that worked, thanks!

Scott Viteri (Aug 16 2020 at 00:09):

Investigating the cause of the string pattern match issue, I find that
#reduce (string.has_decidable_eq "hi" "hi")
yields

619:1: excessive memory consumption detected at 'replace' (potential solution: increase memory consumption threshold)

Scott Viteri (Aug 16 2020 at 00:12):

The same statement with #eval works correctly.
Digging a bit deeper, I receive the same error with

#reduce (list.has_dec_eq "hi".data "hi".data)

Scott Viteri (Aug 16 2020 at 00:13):

(side note: why are the has_dec_eq statements named inconsistently?)

Bryan Gin-ge Chen (Aug 16 2020 at 00:16):

#reduce doesn't work well with strings, see e.g. this thread and this thread.

Scott Viteri (Aug 16 2020 at 00:41):

Ok, but then what is the reason that Mario's is_and function works?

def is_and : term  bool
| (const "and" _) := tt
| _ := ff

Scott Viteri (Aug 16 2020 at 00:42):

#reduce (is_and (const "and" boolsort))
works fine

Reid Barton (Aug 16 2020 at 00:43):

The kernel can see that "and" = "and" without doing much work

Reid Barton (Aug 16 2020 at 00:44):

in particular, without computing the entire normal form of "and"

Scott Viteri (Aug 16 2020 at 00:45):

so pattern matching uses the kernel to check string equality?

Reid Barton (Aug 16 2020 at 00:45):

#reduce uses the kernel to reduce things to normal form

Reid Barton (Aug 16 2020 at 00:47):

in this case, that thing is whatever the equation compiler turned is_and into, applied to its argument

Scott Viteri (Aug 16 2020 at 00:47):

is the issue with normal form on natural numbers that the normal form is written in unary representation or something?

Reid Barton (Aug 16 2020 at 00:48):

yes but it's worse than that: the proof that 'a' is a valid Unicode character is also encoded in unary

Scott Viteri (Aug 16 2020 at 00:49):

where does this notation come from?
n < 0xd800 ∨ (0xdfff < n ∧ n < 0x110000)

Scott Viteri (Aug 16 2020 at 00:49):

namely the hexadecimal

Reid Barton (Aug 16 2020 at 00:51):

I would presume it's built-in just like ordinary decimal numeric literals.

Scott Viteri (Aug 16 2020 at 00:53):

When I write #check 123 should I think of that as a unary nat?

Reid Barton (Aug 16 2020 at 00:53):

I would think of it as 123

Scott Viteri (Aug 16 2020 at 00:54):

Because it infers the type is nat

Reid Barton (Aug 16 2020 at 00:54):

the representation and whether you care what the representation is depends on the context

Reid Barton (Aug 16 2020 at 00:54):

the inferred type also depends on the context, but with no context it's indeed nat

Scott Viteri (Aug 16 2020 at 00:54):

oh really

Scott Viteri (Aug 16 2020 at 00:55):

so there is some code to do this context inference somewhere in the c++?

Reid Barton (Aug 16 2020 at 00:56):

Writing #check 2000000000000000 works fine, so obviously in this situation Lean did not build a unary representation of the number behind the scenes

Scott Viteri (Aug 16 2020 at 00:57):

haha right

Scott Viteri (Aug 16 2020 at 00:58):

It can't be so hard to rewrite char using hexadecimal nat explicitly written as an inductive datatype, no?

Reid Barton (Aug 16 2020 at 01:01):

It would be possible but there's no real application I can think of

Reid Barton (Aug 16 2020 at 01:01):

and it would complicate making #eval treat them efficiently

Reid Barton (Aug 16 2020 at 01:03):

for syntax trees you shouldn't really be using string in the first place--make an inductive type with the values you care about

Scott Viteri (Aug 16 2020 at 01:03):

In this context I want to simulate an open type

Scott Viteri (Aug 16 2020 at 01:04):

I'm adding smt theories dynamically

Scott Viteri (Aug 16 2020 at 01:05):

I really don't want support them all in one big inductive term/sort datastructure, and then edit any def that uses them upon each addition

Scott Viteri (Aug 16 2020 at 01:06):

Now I could replace string matching with nat here, but that will make things more difficult to read

Reid Barton (Aug 16 2020 at 01:06):

or use a type variable

Reid Barton (Aug 16 2020 at 01:07):

but I agree this is a bit more complicated

Scott Viteri (Aug 16 2020 at 01:07):

do you mean if I set the different sorts up as types?

Scott Viteri (Aug 16 2020 at 01:08):

and pattern match on which type

Scott Viteri (Aug 16 2020 at 01:08):

Actually I am unsure what you mean

Reid Barton (Aug 16 2020 at 01:08):

well, I'd have to think about it more but I'm pretty sure there is always a better option than string

Scott Viteri (Aug 16 2020 at 01:09):

fair enough

Scott Viteri (Aug 16 2020 at 01:10):

this is the kind of example that I am looking at

def sortof_aux : term  option sort
| (const str s) :=
  match str with
  | "bot" := boolsort
  | "not" := (arrow boolsort boolsort)
  | "or"  := (arrow boolsort (arrow boolsort boolsort))
  | "and"  := (arrow boolsort (arrow boolsort boolsort))
  | "implies"  := (arrow boolsort (arrow boolsort boolsort))
  | "xor"  := (arrow boolsort (arrow boolsort boolsort))
  | "iff"  := (arrow boolsort (arrow boolsort boolsort))
  | _      := s
  end
| (const str (option.some constructor)  t₁  t₂) :=
  if str = "eq" then
    do s₁  sortof_aux t₁, s₂  sortof_aux t₂,
      if s₁ = s₂ then boolsort else option.none
  else option.none
| (const str (option.some constructor)  t₁  t₂  t₃) :=
  if str = "f_ite" then
    (do s₁  sortof_aux t₁, s₂  sortof_aux t₂, s₃  sortof_aux t₂,
        if s₁ = boolsort  s₂ = s₃ then s₂ else option.none)
  else option.none
| (f  t)  :=
  do sf  sortof_aux f, s  sortof_aux t,
    match sf with
    | (arrow s1 s2) := if s1 = s then s2 else option.none
    | _ := option.none
    end

def sortof : option term  option sort :=
 (flip option.bind) sortof_aux

def x₁ := const "x₁" boolsort
#reduce (sortof x₁) -- recursion error

Scott Viteri (Aug 16 2020 at 01:10):

doesn't work because comparing strings

Reid Barton (Aug 16 2020 at 01:11):

does #eval work?

Scott Viteri (Aug 16 2020 at 01:11):

yes

Scott Viteri (Aug 16 2020 at 01:11):

#eval (sortof x₁) -- (some bool)

Mario Carneiro (Aug 16 2020 at 01:11):

Another option to represent an "open type" would be num

Mario Carneiro (Aug 16 2020 at 01:12):

just define bot := 0 and not := 1 and so on

Mario Carneiro (Aug 16 2020 at 01:13):

You should be able to handle at least a few thousand constructors that way in the kernel

Scott Viteri (Aug 16 2020 at 01:13):

Ok I'm looking at num now

Scott Viteri (Aug 16 2020 at 01:14):

it is zero or pos, and if pos seems to have a binary encoding of nat

Scott Viteri (Aug 16 2020 at 01:14):

why is this better than using nat

Scott Viteri (Aug 16 2020 at 01:14):

efficiency?

Mario Carneiro (Aug 16 2020 at 01:16):

Yes, you can #reduce (200000000000000 : num) in the kernel without too much trouble

Scott Viteri (Aug 16 2020 at 01:17):

I could use a map from strings to their corresponding nums to make it more readable, but I feel like this runs into the same issues as before

Mario Carneiro (Aug 16 2020 at 01:17):

actually I lied, lean can only go up to about (20000 : num), although this is farther than you can go on nat

Mario Carneiro (Aug 16 2020 at 01:18):

In Coq it's pretty common to use pos_num for maps and things in the kernel

Scott Viteri (Aug 16 2020 at 01:18):

I mean 20000 is good enough

Mario Carneiro (Aug 16 2020 at 01:19):

basically you should think of a function out of pos_num as an infinite binary tree

Mario Carneiro (Aug 16 2020 at 01:20):

whereas a function out of nat is more like a list

Mario Carneiro (Aug 16 2020 at 01:20):

so it is easier to store more data, more easily accessible, on the tree representation

Reid Barton (Aug 16 2020 at 01:21):

can you pattern match on num?

Scott Viteri (Aug 16 2020 at 01:22):

I don't see why not

Scott Viteri (Aug 16 2020 at 01:22):

it will be a nested pattern match if it is pos

Scott Viteri (Aug 16 2020 at 01:23):

does pattern match not work on all inductive types?

Reid Barton (Aug 16 2020 at 01:24):

right, I guess it should work fine

Reid Barton (Aug 16 2020 at 01:24):

in that case, you don't need to make a fancy map or anything, you can just write @[pattern] def bot : num = 1 like Mario suggested

Scott Viteri (Aug 16 2020 at 01:25):

Oh

Scott Viteri (Aug 16 2020 at 01:26):

I didn't understand the suggestion

Scott Viteri (Aug 16 2020 at 01:26):

that makes total sense

Scott Viteri (Aug 16 2020 at 01:26):

Thanks!

Mario Carneiro (Aug 16 2020 at 01:26):

actually pos_num is better than num for this

Mario Carneiro (Aug 16 2020 at 01:27):

you don't need 0 here so the pure tree structure is simpler

Scott Viteri (Aug 16 2020 at 01:27):

yeah


Last updated: Dec 20 2023 at 11:08 UTC