Zulip Chat Archive
Stream: lean4
Topic: attributes attached to a declaration
Kevin Buzzard (Dec 04 2022 at 00:33):
How can I see the attributes attached to a declaration? In Lean 3 I could see them with #print
.
Kevin Buzzard (Dec 04 2022 at 15:46):
I hadn't realised that this would be so complicated. What's happening in practice is that I'm trying to understand the behaviour of to_additive
and tags, and the only tags I'm interested in inspecting on a declaration are simp
, norm_cast
and coe
. I'm having to construct examples to check whether tags are there because I can't just look to see what's going on.
Tomas Skrivan (Dec 04 2022 at 18:06):
I know how to go the other way around in a particular case.
Here is a command checking if an instance declaration is marked as default_instance
import Lean
import Lean.Meta
open Lean Elab Command Term Meta
syntax (name := mycommand1) "#is_default_instance" ident : command
@[command_elab mycommand1]
def isDefaultInstanceCommand : CommandElab
| `(#is_default_instance $declId:ident) => do
let env ← getEnv
let decl := declId.getId
-- We can get list of all default instances with:
let di := (defaultInstanceExtension.getState env).defaultInstances
-- to check if `decl` is a default instance we simply do:
let is_default_instance := di.any λ className listOfInstancesAndPrio =>
listOfInstancesAndPrio.any λ (instName, prio) => instName = decl
IO.println s!"Declaration {decl} is default instance: {is_default_instance}"
| _ => throwUnsupportedSyntax
#is_default_instance instHAdd -- true
#is_default_instance HAdd -- false
#is_default_instance instAddFloat -- false
However the approach is quite specialized for default_instance
attribute, for another attribute it would look a bit different. It is not even clear that the corresponding PersistentEnvExtension
has to hold the necessary information.
The chapter on attributes in the meta programming book seems to be empty :( It would be great to have an explanation of TagAttribute
, ParametricAttribute
, EnumAttributes
and what are environment extensions and how are attributes connected.
Jannis Limperg (Dec 05 2022 at 10:52):
I think this functionality (list all attributes for a given constant) is still missing. In Lean 3, the attribute architecture makes this easy to do; in Lean 4, it needs specific support which has not been written yet.
If you're interested in a closed collection of attributes, you can write, for each of them, a function which checks whether the attribute was applied to the constant. The details vary depending on how the attribute stores its data, but it'll be broadly similar to Tomas' example. I would expect such functions to already exist for certain attributes.
Jannis Limperg (Dec 05 2022 at 10:53):
Tomas Skrivan said:
The chapter on attributes in the meta programming book seems to be empty :( It would be great to have an explanation of
TagAttribute
,ParametricAttribute
,EnumAttributes
and what are environment extensions and how are attributes connected.
I'll put this on the todo list, but I'm quite busy right now. Maybe over Christmas.
Jannis Limperg (Dec 05 2022 at 11:02):
Oh, I see there was already a long discussion about this in #mathlib4 > to_additive and coe
Floris van Doorn (Dec 11 2022 at 17:36):
I believe that even if you have a specific attribute, it is hard to check whether it is attached to a declaration. Some attributes don't record to which declarations they are attached. For example, this information is hard to get for @[ext]
, unless you want to navigate the whole DiscTree
.
Jannis Limperg (Dec 12 2022 at 09:59):
I have a std4 PR for folding DiscrTree
s but yeah, O(n) membership query is not great. SimpTheorems
uses a DiscrTree
plus one or two HashMap
s to support erasing and efficient membership queries and I use the same approach for Aesop. Maybe it would be worth packaging this up into a data structure.
Mario Carneiro (Dec 12 2022 at 10:05):
I think a better approach for membership queries is to use the tag attribute's data structure, since that adds no startup cost
Jannis Limperg (Dec 12 2022 at 12:11):
Oh interesting, I didn't know about this optimisation.
Tudor achim (Sep 09 2023 at 21:26):
Consider the following parametric attribute definition (adapted from https://github.com/leanprover/lean4/blob/master/tests/pkg/user_attr/UserAttr/BlaAttr.lean).
Is there any way to get the fully qualified module name from the declName
variable in the second to last line? Right now it prints only the name of the entity that has been tagged.
syntax (name := tag) "tag" str : attr
initialize tagAttr : ParametricAttribute String ←
registerParametricAttribute {
name := `tag
descr := "parametric attribute containing the tag"
getParam := fun _ stx =>
match stx with
| `(attr| tag $tag:str) =>
return (tag.getString)
| _ => throwError "unexpected tag attribute"
afterSet := fun declName tag => do
IO.println s!"<<ATTR>> {declName}:tag:{tag}"
}
Tudor achim (Sep 09 2023 at 23:37):
In fact, I'm having great difficulty registering an attribute that accepts a (String \cross String) parameter; when I try to initialize
the corresponding Attr
it says it's not found. Specifically this kind of code:
syntax (name := kvtag) "kvtag" str str : attr
initialize kvtagAttr : ParametricAttribute (String ⨯ String) ←
registerParametricAttribute {
name := `kvtag
descr := "kvtag"
getParam := fun _ stx =>
match stx with
| `(attr| tag $tag:str $val:str) =>
return (tag.getString, val.getString)
| _ => throwError "unexpected tag attribute"
afterSet := fun declName tag val => do
IO.println s!"<<ATTR>> {declName}:{tag}:{val}"
}
Mac Malone (Sep 10 2023 at 02:07):
Tudor achim said:
| `(attr| tag $tag:str $val:str) =>
This line should be using kvtag
, not tag
.
Tudor achim (Sep 10 2023 at 20:22):
Thanks. That was a small typo, but fixing it led to the main error I've been seeing:
syntax (name := kvtag) "kvtag" str str : attr
initialize kvtagAttr : ParametricAttribute (String ⨯ String) ←
registerParametricAttribute {
name := `kvtag
descr := "kvtag"
getParam := fun _ stx =>
match stx with
| `(attr| kvtag $tag:str $val:str) =>
return (tag.getString, val.getString)
| _ => throwError "unexpected tag attribute"
afterSet := fun declName tag val => do
IO.println s!"<<ATTR>> {declName}:{tag}:{val}"
}
Seems quite strange. If I change it to be a ParametricAttribute
that just takes one parameter then it starts to work again.
Mac Malone (Sep 11 2023 at 02:34):
@Tudor achim The problem is that you are using the wrong cross character for (String x String)
(you are using ⨯
(\X
) instead of the correct ×
(\x
) character). This causes the parse of the initialize
command to fail which then produces strange errors.
Tudor achim (Sep 11 2023 at 17:51):
@Mac Malone : that solved it. I'm confident I wouldn't have gotten it without that feedback :joy:
Tudor achim (Sep 11 2023 at 18:34):
Very interesting -- I think I was actually using \times
not \X
because I'm so used to latex
Tudor achim (Sep 11 2023 at 19:58):
Also wondering if you have thoughts on the original question:
Is there any way to get the fully qualified module name from the declName variable in the second to last line? Right now it prints only the name of the entity that has been tagged.
The idea is to pull the module name, sort of how you can pull that in python with the inspect module in the context of a decorator.
Looking at https://github.com/leanprover/lean4/blob/3aa1cfcceabf7d091a3b2e5d4330df76767336ac/src/Init/Prelude.lean#L3406 it's not clear you can do this the way I have things set up
Patrick Massot (Sep 11 2023 at 20:11):
Tudor achim said:
Very interesting -- I think I was actually using
\times
not\X
because I'm so used to latex
\times
will give you the same thing as \x
. The different one is \X
which is used for cross-products.
Kyle Miller (Sep 11 2023 at 20:12):
You can get the namespace for the declName using declName.getPrefix
-- or do you mean the module, like the file the declaration is defined in?
Tudor achim (Sep 11 2023 at 20:13):
Patrick Massot said:
Tudor achim said:
Very interesting -- I think I was actually using
\times
not\X
because I'm so used to latex
\times
will give you the same thing as\x
. The different one is\X
which is used for cross-products.
Hmm I just tested that and confirmed you're right. No idea how the \X
came in the picture, then, since I never used that in Latex.
Kyle Miller said:
You can get the namespace for the declName using
declName.getPrefix
-- or do you mean the module, like the file the declaration is defined in?
That's correct, I mean the module, similar to what python's inspect
module lets you do from within the decorator function definition.
Kyle Miller (Sep 11 2023 at 20:14):
Comparing with python is a bit confusing, because python fuses the notions of a module and a namespace, but it's definitely possible to get the module for a declaration in Lean.
Tudor achim (Sep 11 2023 at 20:16):
Kyle Miller said:
Comparing with python is a bit confusing, because python fuses the notions of a module and a namespace, but it's definitely possible to get the module for a declaration in Lean.
Thanks! I didn't understand this distinction. I think what I actually wanted was to use namespaces, which I've just switched to, and confirm they have the behavior I was looking for (w.r.t. attributes and the Name
lookup).
Patrick Massot (Sep 11 2023 at 20:17):
A useful trick to get even more help is to post #mwe (that's a link). Currently I cannot copy your code into a fresh Lean file and experience the problem you are facing.
Kyle Miller (Sep 11 2023 at 20:18):
For what it's worth, you can get the filename of the declaration in afterSet
from the third argument (that you called val
) as val.fileName
.
Tudor achim (Sep 11 2023 at 20:18):
For sure, I thought I was doing that but I see I didn't include the headers. In this case it just uses core Lean
import Lean
open Lean
Either way, you've all been super helpful and my issues are resolved. Thanks!
Patrick Massot (Sep 11 2023 at 20:19):
The import
and open
are indeed very important for this.
Tudor achim (Sep 11 2023 at 20:28):
I can't tell if I'm running into arcane Lean issues or just missing something simple. When I write the following code:
import Lean
open Lean
syntax (name := kvtag) "kvtag" str str : attr
initialize kvtagAttr : ParametricAttribute (String × String) ←
registerParametricAttribute {
name := `kvtag
descr := "kvtag"
getParam := fun _ stx =>
match stx with
| `(attr| kvtag $tag:str $val:str) =>
return (tag.getString, val.getString)
| _ => throwError "unexpected tag attribute"
afterSet := fun declName val ctx => do
IO.println s!"<<ATTR>> {declName}:{val} {ctx.fileName}"
}
I get this error:
image.png
From what I can tell, the third argument is a Core.Context
as @Kyle Miller suggested:
Looking at the code for Core.Context
, it seems like fileName
is indeed a String
:
image.png
So I'm really unsure about why the compiler is not accepting this.
Kyle Miller (Sep 11 2023 at 20:29):
It's something to do with IO.println
being incompatible with whatever monad we're in -- when I was testing I switched that to dbg_trace
Kyle Miller (Sep 11 2023 at 20:29):
afterSet := fun declName tag val => do
dbg_trace s!"<<ATTR>> {declName.getPrefix}:{tag}:{val.fileName}"
Tudor achim (Sep 11 2023 at 20:31):
Great, that also worked for me. It's unclear to me why adding another String
triggers an issue with println
, though
Kyle Miller (Sep 11 2023 at 20:31):
I never got IO.println
to work at all myself, no matter what string I gave it
Kyle Miller (Sep 11 2023 at 20:32):
Does IO.println ""
work for you?
Tudor achim (Sep 11 2023 at 20:33):
I think something gets screwed when we add the third argument, it seems to change the expected type.
This works:
open Lean
syntax (name := kvtag) "kvtag" str str : attr
initialize kvtagAttr : ParametricAttribute (String × String) ←
registerParametricAttribute {
name := `kvtag
descr := "kvtag"
getParam := fun _ stx =>
match stx with
| `(attr| kvtag $tag:str $val:str) =>
return (tag.getString, val.getString)
| _ => throwError "unexpected tag attribute"
afterSet := fun declName val => do
IO.println s!"<<ATTR>> {declName}:{val}"
-- dbg_trace s!"<<ATTR>> [{ctx.fileName}] {declName}:{val}"
}
This does NOT:
initialize kvtagAttr : ParametricAttribute (String × String) ←
registerParametricAttribute {
name := `kvtag
descr := "kvtag"
getParam := fun _ stx =>
match stx with
| `(attr| kvtag $tag:str $val:str) =>
return (tag.getString, val.getString)
| _ => throwError "unexpected tag attribute"
afterSet := fun declName val ctx => do
IO.println s!"<<ATTR>> {declName}:{val}"
-- dbg_trace s!"<<ATTR>> [{ctx.fileName}] {declName}:{val}"
}
Patrick Massot (Sep 11 2023 at 20:33):
There is something a bit subtle going on here. In your afterSet
definition, you consume too many arguments
Patrick Massot (Sep 11 2023 at 20:34):
You could use
initialize kvtagAttr : ParametricAttribute (String × String) ←
registerParametricAttribute {
name := `kvtag
descr := "kvtag"
getParam := fun _ stx =>
match stx with
| `(attr| kvtag $tag:str $val:str) =>
return (tag.getString, val.getString)
| _ => throwError "unexpected tag attribute"
afterSet := fun declName val => do
let ctx ← read
IO.println s!"<<ATTR>> {declName}:{val} {ctx.fileName}"
}
Patrick Massot (Sep 11 2023 at 20:34):
There your do
computation is in the Attr
monad which can lift IO
instructions.
Kyle Miller (Sep 11 2023 at 20:34):
Yeah, taking a third argument is breaking the API of the reader monad.
Patrick Massot (Sep 11 2023 at 20:35):
Exactly.
Henrik Böving (Sep 11 2023 at 20:35):
You are operating in AttrM right now. AttrM = CoreM and CoreM can run IO so you can run IO computation there. The issue as patrick says is that you consume too many arguments, that makes your function not be in CoreM anymore but instead in the monad of the remaining CoreM stack so Lean doesn't understand it can lift IO computation there anymore.
Tudor achim (Sep 11 2023 at 20:40):
OK, thanks all, this works now.
In the interest of understanding how to not have to ask these questions in the future, what's the main way to tell that that's what's happening from the source code of ParametericAttribute
? Is it this line that makes it clear afterSet
is only called with two arguments?
Henrik Böving (Sep 11 2023 at 20:41):
With this issue in particular I would say it is to understand how monad stacks work in Lean and that Reader and State monads are just functions. There isn't a need to read the code of the framework itself.
Patrick Massot (Sep 11 2023 at 20:44):
Look at the expected type in your original attempt: it says StateRefT' IO.RealWorld Core.State (EIO Exception) Unit
. This looks really bad. You should never see such a naked monad. All the interfaces you are meant to use are nicely packaged into named monads defined using the abbrev
command so that it's nice to read for you but Lean can see the underlying complexity. So the rule of thumb is: if you see such a thing then you took a wrong turn.
Patrick Massot (Sep 11 2023 at 20:44):
That being said, you clearly jumped into the deep end of Lean. What you do isn't expected from non-expert users. So really you shouldn't be afraid to ask questions.
Last updated: Dec 20 2023 at 11:08 UTC