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