Zulip Chat Archive

Stream: mathlib4

Topic: local attribute [- ...]


Heather Macbeth (Jan 03 2023 at 05:11):

Is there a way of spelling local attribute [-...] in Lean 4? I used to be able to do

import tactic.norm_num

local attribute [-norm_num] norm_num.eval_nat_int_ext

to disable a norm_num extension, this doesn't seem to work in mathlib 4 or maybe I have the wrong syntax?

import Mathlib.Tactic.NormNum.Basic

attribute [-norm_num] Mathlib.Meta.NormNum.evalPow
-- attribute cannot be erased

Heather Macbeth (Jan 03 2023 at 05:11):

That one's for teaching, but there are also a number of uses of local attribute [-instance] in mathlib.

Mario Carneiro (Jan 03 2023 at 05:24):

This seems to be spelled attribute [-foo] now; erasing is only supported locally so there is no syntax like attribute [local -foo]

Mario Carneiro (Jan 03 2023 at 05:25):

the reason it doesn't work for norm_num is because attributes have to explicitly opt-in to being erased and you have to write some extra code to support it. Most mathlib4 tactics don't support erasing right now

Heather Macbeth (Jan 03 2023 at 05:29):

Ah great. Indeed instance erasing works just fine (I guess the fact that it was local automatically is what I hadn't realised):

import Mathlib.Data.Int.Order.Basic

example {a : } {b : } (h1 : 0  a) (h2 : 0  b) : a < b  a * a < b * b :=
  mul_self_lt_mul_self_iff h1 h2 -- works

attribute [-instance] Int.linearOrderedCommRing

example {a : } {b : } (h1 : 0  a) (h2 : 0  b) : a < b  a * a < b * b :=
  mul_self_lt_mul_self_iff h1 h2 -- fails

Heather Macbeth (Jan 03 2023 at 05:30):

How hard would it be to make norm_num erasable? Is there any similar example already implemented that could serve as a model?

Heather Macbeth (Jan 03 2023 at 05:31):

Do you mind if I add this as an issue? (If there's no reason in principle why it couldn't be done?)

Mario Carneiro (Jan 03 2023 at 06:34):

Heather Macbeth said:

Do you mind if I add this as an issue? (If there's no reason in principle why it couldn't be done?)

Yes please, there is no fundamental issue with implementation. A similar example might be simp: you keep a map for erased norm_num extensions in addition to the regular map, and if the regular map produces a result, we first check the erased map to make sure the extension has not been erased, and if it is we skip it

Heather Macbeth (Jan 03 2023 at 08:21):

mathlib4#1308, feel free to make any corrections.

Thomas Murrills (Jan 06 2023 at 01:37):

@Sarah Smith reached out to me about trying this—I'm going to give it a shot, if that's alright :) I'll open a branch + PR for it and start committing!

Thomas Murrills (Jan 06 2023 at 06:43):

If anyone has some good simple examples of norm_num erasure (cases where something can't be proved when something is erased) for the test file, they're welcome here :)

Thomas Murrills (Jan 06 2023 at 06:46):

Also, for anyone familiar with the existing code: is there a reason we don't keep track of the Entry list when we use addImportedFn? Currently it returns pure ⟨[], dt⟩.

I'm a little unsure of when we actually use the list of Entrys, to be honest...

Heather Macbeth (Jan 06 2023 at 06:47):

Here's one:

example : 3 ^ 3 + 4 = 31 := by
  norm_num1
  with_reducible rfl

fails if you put it before the evalPow extension, succeds if you put it after.

Heather Macbeth (Jan 06 2023 at 06:48):

So you'd like it to fail if you disable the evalPow extension.

Thomas Murrills (Jan 06 2023 at 07:06):

It works! :) Up to the above details about the Entry list.

Thomas Murrills (Jan 06 2023 at 07:07):

Is there a way to restore an attribute after erasing it? I would have expected

attribute [norm_num] Mathlib.Meta.NormNum.evalPow

but that doesn't work.

Heather Macbeth (Jan 06 2023 at 07:10):

Can you mark the erasure with

attribute [-norm_num] in
lemma ....

to make it local to that lemma?

Heather Macbeth (Jan 06 2023 at 07:10):

Or enclose the erasure in section/end?

Thomas Murrills (Jan 06 2023 at 07:12):

Hmmm...I would have expected both of those to work, but neither of them do.

Thomas Murrills (Jan 06 2023 at 07:13):

Interestingly it doesn't complain about attribute [-norm_num] ... in, but nonetheless it doesn't work as expected. I wonder if that's something I can fix inside norm_num or if it has to do with how [-_] is elaborated...

Heather Macbeth (Jan 06 2023 at 07:18):

I have no idea, hopefully an expert will comment.

Mario Carneiro (Jan 06 2023 at 07:51):

Thomas Murrills said:

Also, for anyone familiar with the existing code: is there a reason we don't keep track of the Entry list when we use addImportedFn? Currently it returns pure ⟨[], dt⟩.

I'm a little unsure of when we actually use the list of Entrys, to be honest...

addImportedFn is responsible for setting up the initial state of the attribute after importing upstream modules (i.e. just after all the import lines). The first component of the pair is the list of entries defined in the current file, so it starts out empty. The list component is pushed to in addEntryFn, which is called when a new entry is added in the current file, and the list of entries is serialized into an array for future imports in exportEntriesFn

Mario Carneiro (Jan 06 2023 at 07:54):

Thomas Murrills said:

Is there a way to restore an attribute after erasing it? I would have expected

attribute [norm_num] Mathlib.Meta.NormNum.evalPow

but that doesn't work.

No, the only way to restore an erased attribute is to do it in a section and pop the scope

Thomas Murrills (Jan 06 2023 at 08:15):

Gotcha, makes sense!

Reid Barton (Jan 06 2023 at 08:16):

BTW is "erased" the official word for this? It has an unrelated, more common meaning in theorem proving/programming...

Thomas Murrills (Jan 06 2023 at 08:19):

I need a way to check if a name has [norm_num], though. I can't simply check if it's NormNumExt, because that doesn't account for already-erased attributes. The three options I can see are

  1. get all NormNumExt nodes of the DiscrTree (yikes?)
  2. add a field to the state that keeps track of active norm_nums
  3. check if the name isNormNumExt by looking at the environment and check if not already erased

Which of these is best (or should I do something else)?

Thomas Murrills (Jan 06 2023 at 08:20):

Reid Barton said:

BTW is "erased" the official word for this? It has an unrelated, more common meaning in theorem proving/programming...

Yep: - in e.g. [-norm_num] is secretly an eraseAttr syntax node, and the functionality for erasing an attribute is given by including the field erase when registering it :)

Mario Carneiro (Jan 06 2023 at 08:42):

I'm okay with saying that the usage of the word "erase" in this context is dev-only though

Mario Carneiro (Jan 06 2023 at 08:43):

not that I have a better naming suggestion

Mario Carneiro (Jan 06 2023 at 08:44):

@Thomas Murrills in what context do you need to know if a name is @[norm_num] tagged?

Thomas Murrills (Jan 06 2023 at 08:44):

In defining the function erase

Mario Carneiro (Jan 06 2023 at 08:44):

to give an error message if it's not already tagged?

Thomas Murrills (Jan 06 2023 at 08:44):

It should throw an error if the name doesn't have [norm_num], yeah

Thomas Murrills (Jan 06 2023 at 08:45):

At least, patterning off existing erases

Mario Carneiro (Jan 06 2023 at 08:45):

what does simp do?

Thomas Murrills (Jan 06 2023 at 08:46):

simp carries around all of the lemma names it's using in the state, I think?

Thomas Murrills (Jan 06 2023 at 08:46):

My guess is that it has other reasons to do so, though

Mario Carneiro (Jan 06 2023 at 08:47):

oh I see, it's the lemmaNames field

Mario Carneiro (Jan 06 2023 at 08:47):

nope, it is used nowhere else

Mario Carneiro (Jan 06 2023 at 08:47):

except in a test

Thomas Murrills (Jan 06 2023 at 08:49):

oh! well how about that

Thomas Murrills (Jan 06 2023 at 08:49):

but would (3) work? it seems more lightweight

Thomas Murrills (Jan 06 2023 at 08:49):

plus we need to access the environment in the course of giving the erase field anyway, so it's available

Mario Carneiro (Jan 06 2023 at 08:51):

being a NormNumExt is technically unrelated to being a norm num extension, you could always just define a random def with that type

Mario Carneiro (Jan 06 2023 at 08:52):

If you want to avoid the extra state I would prefer (1)

Mario Carneiro (Jan 06 2023 at 08:53):

if it's a rare operation then a linear time operation over the discr tree seems fine, there is a small finite number of norm num extensions anyway

Thomas Murrills (Jan 06 2023 at 08:54):

Ok, nice; do you happen to know of a function to get all such nodes out of the DiscrTree or should I write it? I couldn't see it in DiscrTree.lean.

Thomas Murrills (Jan 06 2023 at 08:57):

(Also just to be clear the only reason I want to err on the side of avoiding the extra state is performance, which I don't have great intuition for—if it's fine to add to the state, that's probably easier, tbh.)

Mario Carneiro (Jan 06 2023 at 08:59):

Jannis had some DiscrTree operations in a PR to std I think

Mario Carneiro (Jan 06 2023 at 09:01):

std4#56

Mario Carneiro (Jan 06 2023 at 09:01):

you can steal the operations out of that and we can clean it up later

Thomas Murrills (Jan 06 2023 at 09:05):

sounds good! :) I'll leave a note.

Thomas Murrills (Jan 06 2023 at 21:51):

Mario Carneiro said:

Thomas Murrills said:

Is there a way to restore an attribute after erasing it? I would have expected

attribute [norm_num] Mathlib.Meta.NormNum.evalPow

but that doesn't work.

No, the only way to restore an erased attribute is to do it in a section and pop the scope

How exactly do you spell the scope pop? I was expecting maybe

namespace norm_num_erase
scoped[norm_num_erase] attribute [-norm_num] ...

but that doesn't seem to be right.

Mario Carneiro (Jan 07 2023 at 00:32):

section stuff
attribute [-norm_num] Mathlib.Meta.NormNum.evalPow

...

end stuff
-- Mathlib.Meta.NormNum.evalPow is not erased

Mario Carneiro (Jan 07 2023 at 00:33):

attribute [-norm_num] Mathlib.Meta.NormNum.evalPow in
example : bla := ...

should have the same effect, if you just want to erase the attribute in one declaration

Thomas Murrills (Jan 07 2023 at 00:53):

Those are what @Heather Macbeth originally suggested, but neither work for some reason

section stuff
attribute [-norm_num] Mathlib.Meta.NormNum.evalPow
end stuff

example : 3 ^ 3 + 4 = 31 := by
  norm_num1
  guard_target =ₛ 3 ^ 3 + 4 = 31 -- no change; still erased
  rfl

Thomas Murrills (Jan 07 2023 at 00:55):

attribute [-norm_num] Mathlib.Meta.NormNum.evalPow in
example := true

example : 3 ^ 3 + 4 = 31 := by
  norm_num1
  guard_target =ₛ 3 ^ 3 + 4 = 31 -- no change; still erased
  rfl

Mario Carneiro (Jan 07 2023 at 01:24):

It could be an issue in how you implemented the erase function? That is how erasing attributes is supposed to work from the user side

Thomas Murrills (Jan 07 2023 at 01:41):

Maybe—I tried to pattern it pretty closely after existing ones, but I’ll poke around and see if I can figure it out.

Thomas Murrills (Jan 07 2023 at 01:55):

Update: really weird (to me): when I try to erase an imported norm_num, it works, but exhibits the above bad behavior. When i try to erase a norm_num extension defined in the same file, I get the error attribute cannot be erased—apparently AttributeImpl.erase is being called.

Thomas Murrills (Jan 07 2023 at 02:30):

My guess currently is that e.g. simp uses a ScopedEnvExtension whereas norm_num just uses a PersistentEnvExtension?

Mario Carneiro (Jan 07 2023 at 02:31):

A ScopedEnvExtension is just a PersistentEnvExtension with extra logic that has been inlined into the norm_num extension

Thomas Murrills (Jan 07 2023 at 02:33):

...Which I guess means that erase needs that extra logic to be inlined as well in order to work correctly? Or am I misinterpreting

Mario Carneiro (Jan 07 2023 at 02:33):

yes, most likely

Thomas Murrills (Jan 07 2023 at 02:34):

Ok, thanks, that's helpful...I think I know where to look now

Thomas Murrills (Jan 07 2023 at 04:47):

Hmm, I'm not totally sure the norm_num attribute does inline that extra logic yet. ScopedEnvExtension uses a StateStack which comes with pushScope and popScope functionality, but I don't see any circumlocutions for the state stack and scopes etc. in norm_num.

Thomas Murrills (Jan 07 2023 at 04:47):

Am I correct in thinking that it's these functions which are responsible for managing the relevant scopes when e.g. section/end occurs?

Mario Carneiro (Jan 07 2023 at 04:47):

yes

Thomas Murrills (Jan 07 2023 at 04:52):

Cool. Given that the extra logic doesn't exist yet, is it still a good idea to inline the necessary logic, or is it better to just switch to a ScopedEnvExtension?

Thomas Murrills (Jan 07 2023 at 04:52):

still not sure what's going on with AttributeImpl.erase, though, and I wonder if it would be magically solved by using a ScopedEnvExtension.

Mario Carneiro (Jan 07 2023 at 04:53):

I saw something about calling .erase on the list of erased attributes on your PR, which seems wrong

Thomas Murrills (Jan 07 2023 at 04:55):

That's when a norm_num extension is added (to make sure it's no longer erased). Not sure it really would ever apply, though.

Thomas Murrills (Jan 07 2023 at 04:56):

Also it's just the hash map .erase, not the attribute's erase

Thomas Murrills (Jan 07 2023 at 04:57):

It's patterned off of what SimpTheorems.addConst does

Mario Carneiro (Jan 07 2023 at 05:03):

that should never happen because you can't add the norm_num attribute to a definition twice

Thomas Murrills (Jan 07 2023 at 05:05):

Yeah, that's what I thought, but I didn't want to fail to account for some edge case I couldn't anticipate

Thomas Murrills (Jan 07 2023 at 05:05):

I mean, it doesn't have anything to do with solving this scoping problem, though, right?

Mario Carneiro (Jan 07 2023 at 05:06):

I thought you were trying to do scopes that way

Thomas Murrills (Jan 07 2023 at 05:07):

I don't follow—which way?

Mario Carneiro (Jan 07 2023 at 05:07):

by erasing things from the map

Mario Carneiro (Jan 07 2023 at 05:07):

which is wrong and explains the issues you were having earlier

Thomas Murrills (Jan 07 2023 at 05:08):

Currently I don't do scopes, is the point, I think.

Mario Carneiro (Jan 07 2023 at 05:09):

to answer your question from before, using ScopedEnvExtension would make sense; the reason I avoided it before was because we didn't need scopes

Thomas Murrills (Jan 07 2023 at 05:10):

Would it make sense to implement scopes just for the hash set erased in the state somehow, without scoping the whole state?

Mario Carneiro (Jan 07 2023 at 05:10):

if there is a way to implement scopes in a more lightweight way which takes advantage of the fact that norm num erasure is very rare and @[norm_num] itself can't be scoped, then that would be good too

Thomas Murrills (Jan 07 2023 at 05:11):

Hmm. Can I inspect the current scope?

Thomas Murrills (Jan 07 2023 at 05:11):

From within IO Unit

Mario Carneiro (Jan 07 2023 at 05:11):

no

Mario Carneiro (Jan 07 2023 at 05:12):

certainly not just with that

Mario Carneiro (Jan 07 2023 at 05:12):

all of lean's state is up in some monad's Context or State type

Thomas Murrills (Jan 07 2023 at 05:13):

Right, I noticed it in e.g. CommandElabM's state, iirc—I wasn't sure if IO extended it or something (or not)

Mario Carneiro (Jan 07 2023 at 05:13):

no it's the other way around, IO is at the very bottom of the monad hierarchy

Thomas Murrills (Jan 07 2023 at 05:14):

ahh

Thomas Murrills (Jan 07 2023 at 05:14):

Hmm. erase operates in AttrM. Is that enough?

Thomas Murrills (Jan 07 2023 at 05:14):

Wait

Thomas Murrills (Jan 07 2023 at 05:14):

Let me read that type again

Mario Carneiro (Jan 07 2023 at 05:14):

AttrM is CoreM, which has a decent amount of state

Mario Carneiro (Jan 07 2023 at 05:15):

but I think you are asking the wrong question

Mario Carneiro (Jan 07 2023 at 05:15):

all of an extension's state has to live inside the EnvExtension

Mario Carneiro (Jan 07 2023 at 05:16):

so if your attribute has a scoping effect then it needs to have a scope stack of some kind

Thomas Murrills (Jan 07 2023 at 05:16):

Ok, makes sense

Thomas Murrills (Jan 07 2023 at 05:16):

But what sort of things can implement that?

Mario Carneiro (Jan 07 2023 at 05:17):

what do you mean?

Thomas Murrills (Jan 07 2023 at 05:17):

How can I get the scope state into the EnvExtension state?

Thomas Murrills (Jan 07 2023 at 05:17):

Or, is that not quite how things flow?

Mario Carneiro (Jan 07 2023 at 05:17):

it's the type variables in PersistentEnvExtension

Mario Carneiro (Jan 07 2023 at 05:17):

(see the doc comment too: docs4#Lean.PersistentEnvExtension)

Thomas Murrills (Jan 07 2023 at 05:21):

Right, ok...so, hmm. Let's say a section or end is encountered. some pushScope or popScope occurs, right? Is that meant to reach in and affect the state somehow?

Mario Carneiro (Jan 07 2023 at 05:21):

hm, looks like ScopedEnvExtensions do have an extra bit of magic: src4#Lean.pushScope

Thomas Murrills (Jan 07 2023 at 05:22):

I saw that! I was wondering how that worked

Mario Carneiro (Jan 07 2023 at 05:22):

section calls that function, which checks a global list of all ScopedEnvExtensions which is pushed to in registerScopedEnvExtension

Mario Carneiro (Jan 07 2023 at 05:23):

so that means that even if you make a scoping-like PersistentEnvExtension you won't get a callback so you can't detect scopes

Thomas Murrills (Jan 07 2023 at 05:23):

I seee

Thomas Murrills (Jan 07 2023 at 05:28):

Hmmm. So is the best option really to turn the whole thing into a ScopedEnvExtension? Or is there some tricky way we could, I don't know, set up a separate scoped extension just for keeping track of erased norm_nums, which the PersistentEnvExtension can keep a reference to? I'm not sure if that's possible, though. It seems like the persistent state can't access any of the information needed.

Thomas Murrills (Jan 07 2023 at 05:31):

Unless—could the auxiliary scoped extension reach in and update a field of the persistent extension when it encountered a new scope?

Mario Carneiro (Jan 07 2023 at 05:32):

you still have a fair amount of control with a scoped env extension, the interface is a little different though

Mario Carneiro (Jan 07 2023 at 05:32):

I don't think it should be a problem to scope only what you want to

Thomas Murrills (Jan 07 2023 at 05:33):

Ok, neat—one thing I noticed was that addImportedFn didn't seem to have a direct analogue type-wise

Thomas Murrills (Jan 07 2023 at 05:34):

This is in theDescr argument to registerScopedEnvExtension

Thomas Murrills (Jan 07 2023 at 05:37):

I thought the OLean fields might be related, but they don't quite do the same thing...

Mario Carneiro (Jan 07 2023 at 05:38):

you can see how they relate in src4#Lean.ScopedEnvExtension.addImportedFn

Thomas Murrills (Jan 07 2023 at 05:44):

Oh I see! That looks like it makes sense. Tyvm for taking the time to explain all this. I'm going to pick this back up sometime tomorrow if I can :)

Thomas Murrills (Jan 09 2023 at 21:29):

Ok, I think I got this working! mathlib4#1364

Thomas Murrills (Jan 09 2023 at 21:37):

(A bit worried about the necessary switch from two foldlM's to ScopedEnvExtension's nested for ... in's when building the tree on import, but I don't know—maybe it's the same behind the scenes?)

Thomas Murrills (Jan 09 2023 at 21:40):

Also, as I'm guessing scoped extensions should be, it's now localizable—though I doubt that will get much, if any, use :)


Last updated: Dec 20 2023 at 11:08 UTC