Zulip Chat Archive
Stream: mathlib4
Topic: synport experimentation
Floris van Doorn (Aug 20 2021 at 20:39):
Using the synport/binport that @Daniel Selsam posted, I did an experiment by taking the synported file of measure_theory/measurable_space_def.lean
, and manually fixing all (most) things until the file compiled (I didn't go through correctly camelCasing names). It was quite some work, but there were a lot of minor things that could probably be done by an improved synport.
After I finished, I realized that this wasn't exactly the experimentation that Daniel asked for, but I'll post my findings here anyway. Hopefully they are helpful.
Many of these points are (probably known) issues with synport, but there are definitely also some things that are me just being unfamiliar with Lean 4 (and binport/synport).
First of all, I'd like to say that both binport and synport are very nice tools that already work very well!
- camel casing errors:
- The field
MeasurableSet'
ofMeasurableSpace
was camel cased in the first occurrence, but not any time later in the file. countable
andfinite
were not capitalized- Ideally lemmas like
measurable_set_empty
are automatically camelCased asmeasurableSet_empty
. - these got an incorrect capital letter when camel casing: (? : MeasurableSet _).Compl, Set.Union, Set.SInter, Set.SUnion, ... (presumably because these are Prop's after unfolding?)
- The field
- Some notation was not working:
⋃
(probably because I'm importing binported files, not synported files? Thenotation3
I saw to declare it also didn't work, presumably for the same reason?)∅
(missing instance?)ᶜ
(no localized notation)
- Tactics that are not implemented:
simpa using
rwa
byCases
;
exacts
rfl
for other reflexive relations
- Notation that is not implemented
‹...›
{ a : α | p }
{x}
- commands that are not implemented
- open_locale/localized
- some declarations (
MeasurableSet.bUnion_decode₂
) have more explicit arguments than the Lean 3 version (⦃⦄
is translated to()
?) - parentheses:
∀ b ∈ s
is translated as∀ b _ : b ∈ s
instead of∀ b (_ : b ∈ s)
assume t (ht : generate_measurable s t)
is translated asfun t ht : generate_measurable s t
- All
simp
calls I've tried gave me the error
(deterministic) timeout at 'typeclass', maximum number of heartbeats (500) has been reached (use 'set_option synthInstance.maxHeartbeats <num>' to set the limit)
-
spacing
- a lot of spaces after identifiers/commands are eaten
- if an argument to a tactic is missing, there is a superfluous space after translation:
have p
->have p
orsimpa using
->simpa using
.
-
λ should sometimes be replaced by @λ
- the default value for
Lt
inPreorder
seems to be missing - The code
theorem subsingleton.MeasurableSet [Subsingleton α] {s : Set α} : MeasurableSet s :=
subsingleton.set_cases MeasurableSet.empty MeasurableSet.univ s
gives the error
ambiguous, possible interpretations
Subsingleton α
Subsingleton (pure α)
I don't know how to specify that I don't want a "pure" inserted.
- In
theorem Set.finite.MeasurableSet {s : Set α} (hs : Finite s) : MeasurableSet s :=
Finite.induction_on hs _root_.MeasurableSet.empty λ ha hsf hsm => hsm.insert _
the _root_
is necessary, because otherwise MeasurableSet
is interpreted as a recursive call of the function...
- Two Lean 4 questions:
- Is there a
#print notation
equivalent? Something like#print notation +
? - What is the equivalent of
open_locale classical
?
- Is there a
Mario Carneiro (Aug 20 2021 at 21:15):
What is the equivalent of
open_locale classical
?
It should be translated to open_locale Classical
Mario Carneiro (Aug 20 2021 at 21:19):
Note that synport is not (yet) elaborator-aware, so things like \lam
-> @\lam
aren't easily addressable right now
Mario Carneiro (Aug 20 2021 at 21:20):
but missing syntax and weird formatting can probably be addressed sooner (although I haven't thought about what to do about weird formatting in core yet)
Sebastian Ullrich (Aug 20 2021 at 21:23):
Floris van Doorn said:
- Is there a
#print notation
equivalent? Something like#print notation +
?
No, but you can use go-to-definition on any use of +
(and any other syntax as long as it's imported and not contributed by native code, like many builtins are without import Lean
)
Mario Carneiro (Aug 20 2021 at 21:26):
go to definition seems to take me to the definition of app
a lot
Sebastian Ullrich (Aug 20 2021 at 21:30):
Haha, that's the one syntax it shouldn't be able to go to since there is no atom to click on. But it's been a while since I've used it.
Mario Carneiro (Aug 20 2021 at 21:31):
I often get go to definition hovers that cover the whole command and go to def
or something. It's not just atoms
Mario Carneiro (Aug 20 2021 at 21:33):
for example, hovering on rcases
here:
def foo := by rcases x
highlights rcases x
and takes me to evalTacticSeq1Indented
Patrick Massot (Aug 20 2021 at 21:36):
It would be really nice to have a more reliable go-to-definition in Lean 4. In Lean 3 it's always very frustrating when it doesn't work or goes to the definition of the tactic that sits at the beginning of the line.
Mario Carneiro (Aug 20 2021 at 21:36):
In
def foo := by exact x
hovering over exact
also highlights exact x
but it takes me to evalExact
, so maybe this has something to do with the elabs
Mario Carneiro (Aug 20 2021 at 21:36):
(since rcases
is not implemented in the first example, it just has a syntax
declaration)
Sebastian Ullrich (Aug 20 2021 at 21:37):
Yes, if syntax and elaborator are separate, go-to-definition goes to the elaborator and go-to-declaration to the syntax
Mario Carneiro (Aug 20 2021 at 21:38):
"no type definition found for 'rcases'"
Mario Carneiro (Aug 20 2021 at 21:39):
in #check `(by rcases p)
, hovering on rcases
highlights the whole quotation and goes to elab_stx_quot Parser.Term.quot
Sebastian Ullrich (Aug 20 2021 at 21:40):
Oh yeah, quotations are not special-cased yet
Mario Carneiro (Aug 20 2021 at 21:41):
what special case is needed? at the syntax level, the rcases
parser is called just the same
Sebastian Ullrich (Aug 20 2021 at 21:41):
Mario Carneiro said:
"no type definition found for 'rcases'"
It's go-to-declaration, my bad
Mario Carneiro (Aug 20 2021 at 21:42):
That one goes to Parser.Tactic.tacticSeq1Indented
Sebastian Ullrich (Aug 20 2021 at 21:43):
If it's not implemented yet, that's kind of correct, just not very helpful
Mario Carneiro (Aug 20 2021 at 21:44):
the parser is implemented, but there is no elab (you get an error saying tactic 'Lean.Parser.Tactic.rcases' has not been implemented
)
Sebastian Ullrich (Aug 20 2021 at 21:44):
Yes, that's what I meant
Mario Carneiro (Aug 20 2021 at 21:45):
what's the logic behind the kind-of correctness? Is it going up the tree to find something that was elabbed?
Sebastian Ullrich (Aug 20 2021 at 21:47):
Yes. Many syntax kinds are merely a part of a notation, so we use the surrounding elaborator if any. In quotations, we should just go to the syntax at point instead. The app
case might also stem from this.
Sebastian Ullrich (Aug 20 2021 at 21:48):
It would be great to stop the search at category parser calls, which would fix the tacticSeq1Indented
case, but we don't have that kind of information
Mario Carneiro (Aug 20 2021 at 21:49):
hm, it should be possible to also handle the case where an elaborator is expected but missing, like rcases
, and go to the syntax for that node instead of the elab if none is available
Sebastian Ullrich (Aug 20 2021 at 21:50):
Oh right, we could record missing elaborators
Mario Carneiro (Aug 20 2021 at 21:50):
and syntax-at-point is useful regardless - that should be an option anywhere (maybe under the go-to-declaration handler)
Sebastian Ullrich (Aug 20 2021 at 21:51):
It is!
Mario Carneiro (Aug 20 2021 at 21:51):
right now it looks like syntax-for-elaborator-at-parent-of-point
Sebastian Ullrich (Aug 20 2021 at 21:52):
Ah yes, I think you are right. Yeah, I'd be happy to replace that one.
Mario Carneiro (Aug 20 2021 at 21:52):
(I also need to set up a key combination for that, since it seems like VSCode doesn't bind anything to it)
Sebastian Ullrich (Aug 20 2021 at 21:54):
Haven't even found it in Emacs at all yet, if it is actually exposed
Sebastian Ullrich (Aug 20 2021 at 21:56):
In any case, do open issues for stuff like this!
Mario Carneiro (Aug 20 2021 at 21:58):
@Floris van Doorn I pushed a bunch of changes (some from yesterday) that I wish you had available for your test. Some of the issues you mention are already fixed:
- open_locale/localized
ᶜ
‹...›
- the default value for Lt in Preorder seems to be missing
Mario Carneiro (Aug 20 2021 at 22:01):
For the unimplemented tactics and notation, we are depending on the community to step up and start filling them in. So far I've mostly been trying to avoid outright failures (these are marked by an -- error
comment followed by some code that is obviously not correct lean 4 code) and missing things
Mario Carneiro (Aug 20 2021 at 22:02):
some declarations (MeasurableSet.bUnion_decode₂) have more explicit arguments than the Lean 3 version (⦃⦄ is translated to ()?)
Lean 4 got strict implicits recently, they haven't been hooked up yet fixed
Floris van Doorn (Aug 20 2021 at 22:11):
Mario Carneiro said:
What is the equivalent of
open_locale classical
?It should be translated to
open_locale Classical
Presumably after the community has implemented open_locale
? Or did you also implement the command (not just the notation?).
I was mostly wondering how to do attribute [instance] classical.dec
in Lean 4 (I haven't tried it, so maybe this code just works).
Floris van Doorn (Aug 20 2021 at 22:11):
Do you know why simp
gave me this type-class error? (this would happen on simp
without arguments)
Floris van Doorn (Aug 20 2021 at 22:13):
Nice that you've already implemented a couple of my missing points.
Floris van Doorn (Aug 20 2021 at 22:16):
Mario Carneiro said:
For the unimplemented tactics and notation, we are depending on the community to step up and start filling them in. So far I've mostly been trying to avoid outright failures (these are marked by an
-- error
comment followed by some code that is obviously not correct lean 4 code) and missing things
That makes sense. I'll work on porting some stuff to Lean 4 at some point! Is there anything that is useful to have first? Maybe @[to_additive]
and @[simps]
since they create new declarations themselves?
Mario Carneiro (Aug 20 2021 at 22:16):
Yes, open_locale
will need to be implemented. This is the biggest issue with getting error-free synported files right now - there is no hope of elaborating the files when none of the called tactics exist
Mario Carneiro (Aug 20 2021 at 22:18):
Everything in the Mathport/Prelude/Syntax.lean
file needs to be implemented at some point. I would personally focus on the easy tactics first and work my way to the bigger ones, since that will probably get more things working earlier
Mario Carneiro (Aug 20 2021 at 22:20):
But things like open_locale
and notation3
are particularly important because they can declare new syntax, without which the file won't even parse
Mario Carneiro (Aug 20 2021 at 22:21):
notation3
should work already, but there might be an issue with the camel case heuristics (which are more problematic in notation since those are elaborated lazily in lean 3)
Mario Carneiro (Aug 20 2021 at 22:22):
Floris van Doorn said:
Do you know why
simp
gave me this type-class error? (this would happen onsimp
without arguments)
My guess is that some translated @[simp]
lemma is poison to lean 4 simp
Mario Carneiro (Aug 20 2021 at 22:23):
Floris van Doorn said:
I was mostly wondering how to do
attribute [instance] classical.dec
in Lean 4 (I haven't tried it, so maybe this code just works).
I believe the @[instance]
attribute still exists in lean 4, so I think the same code works
Mario Carneiro (Aug 20 2021 at 22:25):
btw localized has the snazzy new notation
localized [Classical] attribute [instance] Classical.dec
no more string quotes needed! (Also be amazed at how we're correctly translating the contents of a string literal that is double-parsed by lean. No external lean 3 parser would be able to achieve this.)
Daniel Selsam (Aug 20 2021 at 22:45):
Floris van Doorn said:
- some declarations (
MeasurableSet.bUnion_decode₂
) have more explicit arguments than the Lean 3 version (⦃⦄
is translated to()
?)
The f
argument seems to be correctly translated to strict-implicit in Lean4, but strict-implicits were only added to lean4 a few weeks ago and haven't been stress-tested. Can you please be more specific about what behaved unexpectedly?
Daniel Selsam (Aug 20 2021 at 22:49):
Floris van Doorn said:
- All
simp
calls I've tried gave me the error(deterministic) timeout at 'typeclass', maximum number of heartbeats (500) has been reached (use 'set_option synthInstance.maxHeartbeats <num>' to set the limit)
The current Lean4 default for synthInstance.maxHeartbeats
makes sense for programming applications but is rather ambitious for Mathlib. I should have mentioned that for now I start every file with set_option synthInstance.maxHeartbeats 5000
. You may also need to increase the maxHeartbeats
flag as well (the default is 50,000 and 200,000 seems reasonable).
Leonardo de Moura (Aug 20 2021 at 22:51):
@Floris van Doorn Sorry, I didn't read the whole thread, but I want to remind you that we have scoped instances, notation, unification hints, and simp theorems in Lean 4. Scoped elements are activated when we open the namespace. Exaple:
noncomputable def foo (f g : Nat → Nat) : Nat :=
if f = g then 1 else 2 -- Error
open Classical
noncomputable def boo (f g : Nat → Nat) : Nat :=
if f = g then 1 else 2 -- Ok
Leonardo de Moura (Aug 20 2021 at 22:57):
You can find more examples in the lean4 tests
folder directory. Here are some examples:
scoped syntax:max term noWs "[" term ", " term "]" : term
@[scoped simp] axiom ax1 (x : Nat) : f (g x) = x
scoped infix:65 (priority := default+1) "+" => f
scoped instance : ToString A where
toString a := s!"A.mk {a.x} {a.y}"
noncomputable scoped instance (priority := low) propDecidable (a : Prop) : Decidable a :=
choice <| match em a with
| Or.inl h => ⟨isTrue h⟩
| Or.inr h => ⟨isFalse h⟩
scoped macro_rules | `(bar! $x) => `($x + 10)
scoped unif_hint (s : Magma) where
s =?= Nat.Magma |- s.α =?= Nat
Leonardo de Moura (Aug 20 2021 at 22:59):
Mario Carneiro said:
But things like
open_locale
andnotation3
are particularly important because they can declare new syntax, without which the file won't even parse
Lean 4 has scoped attributes. Why do you need open_locale
? Is there something missing in the new scoped attribute feature?
Daniel Selsam (Aug 20 2021 at 23:13):
Floris van Doorn said:
- The code
theorem subsingleton.MeasurableSet [Subsingleton α] {s : Set α} : MeasurableSet s := subsingleton.set_cases MeasurableSet.empty MeasurableSet.univ s
gives the error
ambiguous, possible interpretations Subsingleton α Subsingleton (pure α)
I don't know how to specify that I don't want a "pure" inserted.
Setting pp.all true
provides more information:
error: ambiguous, possible interpretations
Subsingleton.{?u.15 + 1} α
@Set.Subsingleton.{?u.15 + 1} (Type ?u.15)
(@Pure.pure.{?u.15 + 1, ?u.15 + 1} Set.{?u.15 + 1}
(@Applicative.toPure.{?u.15 + 1, ?u.15 + 1} Set.{?u.15 + 1}
(@Monad.toApplicative.{?u.15 + 1, ?u.15 + 1} Set.{?u.15 + 1} Set.monad.{?u.15 + 1}))
(Type ?u.15) α)
It can be made to work by prefixing _root_.Subsingleton
or by not opening the Set
namespace. I don't have time to investigate right now but Set
is a Monad
and the pure
-injection may be a little too strong here. We may not even want to align Mathlib's monad
with Lean4's at all. Also, it is a shame that this isn't visible with default pp settings: the new smart pretty-printer is actually still very dumb regarding open
namespaces, since I only tested it on roundtripping from the root namespace.
Daniel Selsam (Aug 20 2021 at 23:24):
Floris van Doorn said:
- In
theorem Set.finite.MeasurableSet {s : Set α} (hs : Finite s) : MeasurableSet s := Finite.induction_on hs _root_.MeasurableSet.empty λ ha hsf hsm => hsm.insert _
the
_root_
is necessary, because otherwiseMeasurableSet
is interpreted as a recursive call of the function...
Minimized version:
def Foo.empty : Unit := ()
def Bar.Foo : Unit := Foo.empty -- error: invalid field 'empty', the environment does not contain 'Unit.empty'
I agree this is surprising.
Leonardo de Moura (Aug 20 2021 at 23:52):
The example above may look surprising to Lean 3 users, but it is consistent with the name resolution procedure we designed for Lean 4.
Recall that
def Bar.Foo : Unit := Foo.empty
is expanded to
namespace Bar
def Foo : Unit := Foo.empty
end Bar
In Lean 4, you don't need to use equations to write recursive functions. As in Lean 3, local declarations have precedence over global ones. So, Foo
is the body of the definition is referring to the Foo
being defined.
Then, since Foo
has type Unit
, the dot-notation complains that there is no Unit.empty
.
Floris van Doorn (Aug 21 2021 at 04:24):
Leonardo de Moura said:
Floris van Doorn Sorry, I didn't read the whole thread, but I want to remind you that we have scoped instances, notation, unification hints, and simp theorems in Lean 4. Scoped elements are activated when we open the namespace. [...]
Oh yes, I forgot to ask about this. I agree that with the scoped behavior I don't think we need the localized / open_locale commands anymore. From your example it is clear that both scoped notation and scoped instances work, which are the only uses in mathlib.
@Mario Carneiro do you agree we can get rid of localized / open_locale?
Floris van Doorn (Aug 21 2021 at 04:36):
@Daniel Selsam thanks for your responses.
- About
Singleton
/Set.Singleton
: Ah, that makes sense. I think having a coercionα → Set α
is undesirable, so we should consider makingSet.Monad
a scoped instance.
Mario Carneiro (Aug 21 2021 at 04:51):
Leonardo de Moura said:
Mario Carneiro said:
But things like
open_locale
andnotation3
are particularly important because they can declare new syntax, without which the file won't even parseLean 4 has scoped attributes. Why do you need
open_locale
? Is there something missing in the new scoped attribute feature?
My plan, at least, was to translate open_locale
directly to a new open_locale
command, at least at first, just to keep the translation simple and literal. Scoped attributes can probably replace open_locale
, but the implementation is sufficiently syntactically different that it seems easier to do that in a separate refactor after the port. Perhaps there is a way to backport uses of open_locale
in mathlib such that they can be more directly replaced by scoped notation.
Mario Carneiro (Aug 21 2021 at 05:12):
Leonardo de Moura said:
The example above may look surprising to Lean 3 users, but it is consistent with the name resolution procedure we designed for Lean 4.
One of the issues I have hit with this name resolution procedure is that you can't have a definition def foo
that makes use of def foo.aux
, because it always resolves to the local variable foo
. In lean 3, namespaced names take precedence over dot notation, so most of the time this works fine; in equation compiler definitions you can hit this issue because foo
is a local variable, but you can still use @foo.aux
to refer to it:
def foo.aux := 1
def foo : ℕ → ℕ
| n := @foo.aux
In lean 4 I don't know any workaround other than using _root_
or some other namespace.
I think namespaced names should take precedence over dot notation even on local variables (which is not the case in lean 3 or lean 4 ATM), since you can always force dot notation by using any of the other notations for it - foo .aux
, foo |>.aux
, (foo).aux
, while there is no such backup option for namespaced names (besides using a different name to refer to it).
Mario Carneiro (Aug 21 2021 at 05:35):
To say more about the open_locale
issue, in mathlib open classical
and open_locale classical
currently do different things, and in particular the second does not imply the first. Excessive use of open
leads to frequent name clashes, and I tend to avoid them in most mathlib code I write. So we may need to refactor mathlib such that locales differ from namespaces, and are specifically tailored for the notations, like the old eq.ops
namespace from lean 2.
Leonardo de Moura (Aug 21 2021 at 14:23):
I think namespaced names should take precedence over dot notation even on local variables (which is not the case in lean 3 or lean 4 ATM),
I don't agree.
Here are different ways to write your foo
example above in Lean4:
def foo : Nat → Nat
| n => _root_.foo.aux
open foo in
def foo : Nat → Nat
| n => aux
def foo : Nat → Nat
| n => open foo in aux
macro n:ident noWs "#" noWs s:ident : term =>
`(open $n:ident in $s:ident)
def foo : Nat → Nat
| n => foo#aux
Note that we can create many different variants of the last one using macros. This is just a simple one.
Leonardo de Moura (Aug 21 2021 at 14:24):
Mario Carneiro said:
To say more about the
open_locale
issue, in mathlibopen classical
andopen_locale classical
currently do different things, and in particular the second does not imply the first. Excessive use ofopen
leads to frequent name clashes, and I tend to avoid them in most mathlib code I write. So we may need to refactor mathlib such that locales differ from namespaces, and are specifically tailored for the notations, like the oldeq.ops
namespace from lean 2.
I added the command open scoped
that will activate the scoped attributes, but will not "open" the names.
open scoped Classical
#check @epsilon -- Error
noncomputable def foo (f g : Nat → Nat) : Nat :=
if f = g then 1 else 2 -- Ok
Daniel Selsam (Aug 21 2021 at 16:04):
Floris van Doorn said:
Daniel Selsam thanks for your responses.
- About
Singleton
/Set.Singleton
: Ah, that makes sense. I think having a coercionα → Set α
is undesirable, so we should consider makingSet.Monad
a scoped instance.
I don't know what mathlib uses that instance for, but it would be great if the scoping could be backported.
Mario Carneiro (Aug 21 2021 at 18:28):
Leonardo de Moura said:
I added the command
open scoped
that will activate the scoped attributes, but will not "open" the names.
Thanks. So now open_locale foo
can be translated to open scoped Foo
. For localized, we would want to translate localized "cmd" in foo
to scoped cmd
, but with the constraint that we must be in namespace foo
when we do it. I think we should backport this, by introducing localized "cmd"
without the in foo
part, which uses the current namespace (a la #where
) to determine what foo
should be (and errors in the global namespace).
Floris van Doorn (Aug 21 2021 at 19:05):
Daniel Selsam said:
I don't know what mathlib uses that instance for, but it would be great if the scoping could be backported.
I expect it is used very little. I'll take a look.
Floris van Doorn (Aug 21 2021 at 19:28):
Leonardo de Moura said:
I think namespaced names should take precedence over dot notation even on local variables (which is not the case in lean 3 or lean 4 ATM),
I don't agree.
Here are different ways to write yourfoo
example above in Lean4:
[...]
Note that we can create many different variants of the last one using macros. This is just a simple one.
These accidental self-references occur a lot in mathlib. The statement that object foo
has property bar
is usually called bar.foo
(e.g. docs#continuous.max), so that we can use the projection notation. Often in the proof foo
is mentioned explicitly. So the more common example of a clash will look like this
def Foo (n : Nat) : Prop := True
def aux := 1
def Foo.aux : Foo aux :=
have h : aux = 1 := rfl
trivial
For example, these are all lemmas in a single file with accidental self-references: (at least on the current commit 897e4ed
)
src#set.ord_connected.measurable_set
src#ae_measurable.max
src#ae_measurable.min
src#measurable.is_lub
src#ae_measurable.is_lub
src#measurable.is_glb
src#ae_measurable.is_glb
We could probably make a macro that #foo
means _root_.foo
, but I'm still worried that it will confuse users.
Leonardo de Moura (Aug 21 2021 at 20:47):
@Floris van Doorn This is very unfortunate. We have discussed the name resolution in the past and decided that local declarations should take precedence. Note that we did not change that in Lean 4. It looks like a new problem, but it is due to two new features that were requested by users.
1) def Foo.f ... := ...
expands to namespace Foo def f ... := ... end Foo
2) We don't need to use equations to write recursive declarations.
To change the name resolution procedure now, we would have to carefully analyze all new issues (and counterintuitive behavior) the change would produce, and carefully spec how it is supposed to work. Even if we do all this work, I can see other users complaining that they have to write foo .aux
, foo |>.aux
, or (foo).aux
because local variables do not take precedence.
Remarks:
- If we add a mechanism for stating that a declaration is not recursive (i.e., disabling feature 2 above), then the problem is gone.
- The
protected
keyword is another possible workaround. The local declaration will not be atomic.
Mario Carneiro (Aug 21 2021 at 20:52):
Even if we do all this work, I can see other users complaining that they have to write foo .aux, foo |>.aux, or (foo).aux because local variables do not take precedence.
Note that foo.aux
should still work to refer to field projection of aux
on local variable foo
, provided that foo.aux
does not also resolve to something in the current namespace/opens. Since most local variables have lowercase names and most namespaces have uppercase names, I think that collisions of this sort should be rare, but when they do occur, a single character to disambiguate them seems like a small price to pay.
Leonardo de Moura (Aug 21 2021 at 20:52):
@Floris van Doorn Consider the following example you linked
lemma ae_measurable.min {f g : δ → α} {μ : measure δ}
(hf : ae_measurable f μ) (hg : ae_measurable g μ) : ae_measurable (λ a, min (f a) (g a)) μ :=
⟨λ a, min (hf.mk f a) (hg.mk g a), hf.measurable_mk.min hg.measurable_mk,
eventually_eq.comp₂ hf.ae_eq_mk _ hg.ae_eq_mk⟩
Unless we add the feature stating that ae_measurable.min
is not recursive, the min
inside will still refer to the lemma being defined.
Mario Carneiro (Aug 21 2021 at 20:55):
Another issue caused by (1) is that it is impossible to have a mutual def between declarations named foo
and foo.aux
Leonardo de Moura (Aug 21 2021 at 20:56):
@Mario Carneiro The example above shows that the change in the name resolution procedure will not even fix the problem.
Mario Carneiro (Aug 21 2021 at 20:56):
because you can't put a namespace declaration in the middle of a mutual block
Leonardo de Moura (Aug 21 2021 at 20:57):
Mario Carneiro said:
Another issue caused by (1) is that it is impossible to have a mutual def between declarations named
foo
andfoo.aux
Let's focus on the problems that affect Mathlib.
Mario Carneiro (Aug 21 2021 at 20:57):
I think that (1) is a bit too aggressive for a lot of uses, but I would like to get more objective data on the relative benefits
Mario Carneiro (Aug 21 2021 at 20:58):
in particular once you are in the def there is no way to "cancel" the namespace that you have been put into
Mario Carneiro (Aug 21 2021 at 20:59):
and so you basically get problems similar to overuse of open
Mario Carneiro (Aug 21 2021 at 21:06):
Looking at ae_measurable.min
, I think it would be fine to use _root_.min
in that particular case. We already have plenty of other examples like it which use the equation compiler, and we just use _root_
in that case
Leonardo de Moura (Aug 21 2021 at 21:06):
@Mario Carneiro This kind of discussion is very time-consuming. It takes time to find holes in the suggestions. I will try to summarize the thread
- The change in the name resolution procedure will not fix the problem. See
ae_measurable.min
example above. - Feature (1) was supported by many users. If I remember correctly @Reid Barton was the first one that suggested it. We had written a lot of Lean code, and can confirm it works really well.
Mario Carneiro (Aug 21 2021 at 21:09):
Feature (1) was supported by many users. If I remember correctly @Reid Barton was the first one that suggested it. We had written a lot of Lean code, and can confirm it works really well.
Hence the need to get examples together that show the advantages and disadvantages. Clearly both options have downsides, and I don't know what the relative magnitude of the downsides are, and the cost of the workarounds when you get the wrong thing by default and want the other one
Mario Carneiro (Aug 21 2021 at 21:13):
What kind of code benefits the most from (1) and/or would be much more verbose without it? The simple desugaring suggests that it is possible to get the effect of (1) with a def-like macro (possibly even switching the names around so that def
is the macro and def'
is the core command)
Mario Carneiro (Aug 21 2021 at 21:14):
it would be easy enough to have synport produce def'
instead of def
whenever the name of the def contains a dot
Floris van Doorn (Aug 21 2021 at 21:16):
I agree that this is an unfortunate situation, and I don't know the best solution.
An option to disable (2) would be ok (so that you have to write at least one equation | ... => ...
to enable a recursive call), but it's not ideal. (I expect we will want to use this option in most mathlib files).
Leonardo de Moura (Aug 21 2021 at 21:23):
Floris van Doorn said:
I agree that this is an unfortunate situation, and I don't know the best solution.
An option to disable (2) would be ok (so that you have to write at least one equation| ... => ...
to enable a recursive call), but it's not ideal. (I expect we will want to use this option in most mathlib files).
Yes, the | ... => ...
style was criticized a lot in the past. We should not go back to it.
One option is to still use the name ae_measuable.min
for the local declaration even after we perform the macro expansion on theorem ae_measurable.min
. But some users may protest that def Foo.f ... := ...
would not be equivalent to namespace Foo def f ... := ... end Foo
anymore.
Leonardo de Moura (Aug 21 2021 at 21:27):
Mario Carneiro said:
it would be easy enough to have synport produce
def'
instead ofdef
whenever the name of the def contains a dot
This might work. We would also have theorem'
, correct?
Mario Carneiro (Aug 21 2021 at 21:28):
yes, and instance'
, example'
etc. This also seems a bit unfortunate but workable
Mario Carneiro (Aug 21 2021 at 21:30):
To be clear, I'm not prescribing any particular option, and I hope we can find something that works the best for lean 4 users even if mathlib has to change. To my lean 3 colored perspective the fact that the name of a def only affects the name itself and not the body of the definition makes its own kind of sense, and I would present that point of view to anyone protesting about the lack of equivalence of def Foo.f
and namespace Foo def f
. But there seem to be no perfect options here.
Floris van Doorn (Aug 21 2021 at 21:37):
Leonardo de Moura said:
One option is to still use the name
ae_measuable.min
for the local declaration even after we perform the macro expansion ontheorem ae_measurable.min
.
I think this will solve 90+% of the cases in mathlib.
There will be still a couple of problematic cases, like src#con_gen, src#magma.free_semigroup, src#localization, but it's rare that a declaration foo
uses a declaration foo.bar
in it's definition/proof.
Another potential problem is that we are in a namespace foo
and define bar
, while _root_.bar
also exists (and is used in foo.bar
), but usually we protect declarations foo.bar
like this, so then there should be no name clash.
Mario Carneiro (Aug 21 2021 at 21:39):
Is it possible for local declarations to have names with dots? AFAIR this is illegal in lean 3
Leonardo de Moura (Aug 21 2021 at 21:40):
@Mario Carneiro @Floris van Doorn What about a nonrec
modifier for declarations? We already have partial
. The macro lemma
would expand to nonrec theorem
. I think most of the examples that Floris linked are lemma
s.
EDIT: missed
def free_semigroup (α : Type u) [has_mul α] : Type u :=
quot $ free_semigroup.r α
Mario Carneiro (Aug 21 2021 at 21:41):
ooh, that's nice too
Floris van Doorn (Aug 21 2021 at 21:42):
I would be happy with that.
Mario Carneiro (Aug 21 2021 at 21:42):
well, not sure about using lemma
for this, I think the mathematicians have their own opinions about when to apply this label that probably don't correlate with nonrec
Mario Carneiro (Aug 21 2021 at 21:42):
but using nonrec lemma
directly should be fine
Floris van Doorn (Aug 21 2021 at 21:44):
As I said, the declarations I linked in that message are rare. I just happened to make a list of declarations foo
that used foo.bar
in their definition, so I had some potential examples lying around.
I would be ok with requiring these def
s to be replaced by nonrec def
EDIT: Oh wait, with the new suggestion we do run into the problems that in def foo.bar :=
the declaration bar
is a recursive call. That might be more frequent.
Sebastian Ullrich (Aug 21 2021 at 21:50):
Leonardo de Moura said:
As in Lean 3, local declarations have precedence over global ones.
I just wanted to mention that we already have a special case in name resolution for recursive references: in x.f
, f
may be a recursive reference if its (the current) namespace is the type of x
. And that makes sense of course (apart from being very convenient) given that users think of recursive references as global references, not local ones - as if f
was already part of the environment. Then would it not be consistent to extend the special case to the reverse case as well - to interpret f.x
as the global reference f.x
, if it exists, in favor of a projection of the recursive reference (which I don't think I have ever seen used anyway)? IOW, I believe it would be consistent and convenient to prefer global to local references in the auxDecl
special case.
Sebastian Ullrich (Aug 21 2021 at 21:57):
More generally we could have name resolution "pretend" that Current.Namespace.f
is already in the environment, such that references such as _root_.Current.Namespace.f
and Namespace.f
work as well
Sebastian Ullrich (Aug 21 2021 at 22:02):
(This would not resolve the min
case, no)
Leonardo de Moura (Aug 21 2021 at 22:14):
@Floris van Doorn What about combining @Sebastian Ullrich suggestion and having norec
modifier (for examples like min
). It should solve all cases, correct?
Mario Carneiro (Aug 21 2021 at 22:15):
I think this will work
Floris van Doorn (Aug 21 2021 at 22:24):
Yes, that sounds good to me. I think we still want the nonrec
modifier a lot, for cases like ae_measurable.min
, so I think we want it to be the default in mathlib for lemma
and/or theorem
(but as long as we can do that in mathlib by macros, I'd be happy)
Daniel Selsam (Aug 21 2021 at 23:54):
Floris van Doorn said:
Daniel Selsam thanks for your responses.
- About
Singleton
/Set.Singleton
: Ah, that makes sense. I think having a coercionα → Set α
is undesirable, so we should consider makingSet.Monad
a scoped instance.
BTW with https://github.com/leanprover/lean4/pull/643 your example gives the following error with default settings:
error: ambiguous, possible interpretations
_root_.Subsingleton α
Set.Subsingleton (pure α)
Leonardo de Moura (Aug 22 2021 at 00:08):
@Mario Carneiro @Floris van Doorn Pushed the changes. We can now write
def foo.aux := 1
def foo : Nat → Nat
| n => foo.aux
and
def Foo (n : Nat) : Prop := True
def aux := 1
nonrec def Foo.aux : Foo aux :=
have h : aux = 1 := rfl
trivial
Eric Wieser (Jan 24 2023 at 11:22):
So does this mean that
nonrec def Foo.aux : Foo aux :=
have h : aux = 1 := rfl
trivial
is the preferred spelling, and we should prefer not to write the following?
def Foo.aux : Foo aux :=
have h : _root_.aux = 1 := rfl
trivial
Reid Barton (Jan 24 2023 at 11:24):
I don't think we really need a "preferred spelling" here
Last updated: Dec 20 2023 at 11:08 UTC