Zulip Chat Archive
Stream: mathlib4
Topic: Self-correcting code
Damiano Testa (Jun 13 2024 at 12:43):
This post is a short summary with some ideas about self-correcting Lean code.
Since Mathlib changes so quickly, I think that it is important that Mathlib itself, and the projects that depend on it, can be automatically updated as much as possible.
Damiano Testa (Jun 13 2024 at 12:43):
My experience so far is limited, but promising:
- replacement of
refine'
withrefine
, where simply adding?
in the right places was enough; - removal of unnecessary
set_option ... in
; - adding
(since "YYYY-MM-DD")
todeprecated
that were missing it; - renames after a deprecation.
Damiano Testa (Jun 13 2024 at 12:43):
In all of these cases, this is what the workflow has been.
- Plant code in
Mathlib
that guides where the changes are supposed to take place and what the change should be (e.g. a line/column number, some old-name/new-name pair, the more specific the better) -- this has been a linter in the cases above. - Run
lake build
to retrieve all the information about the replacements. - Write a parser for the output of
lake build
that takes the information that was planted and acts on it.
Damiano Testa (Jun 13 2024 at 12:44):
For this to work, the simplest is to have "atomic" changes: add since ...
, shorten long lines, remove a set_option ... in
. The simplest the change, the higher the success rate.
Damiano Testa (Jun 13 2024 at 12:44):
Ideally, another tool (probably human for quite some time!), would compute something like the "edit distance" for a PR, that fragments it into a sequence of elementary changes that ideally fit well with the program above. Such a tool could be an evolution of Alex Best's leaff (of which move-decls
is a very primitive, text-based butchering).
Johan Commelin (Jun 13 2024 at 13:08):
@Mario Carneiro Had similar ideas for a lake exe refactor
. I think it would be really great to have such a tool!
Damiano Testa (Jun 13 2024 at 13:12):
I also just remembered about the replacement of theorem
with lemma
, whenever theorem
does not have a doc-string.
Damiano Testa (Jun 13 2024 at 13:16):
In case anyone is feeling adventurous and wants to automate some deprecations, I would be very happy if they used lake exe update_deprecations (instructions in the repo) and reported back any short-comings/bugs!
Mario Carneiro (Jun 19 2024 at 22:40):
@Johan Commelin An initial draft of lake exe refactor
is ready at #13973 . It would be good to try using this to do one of those back burner refactorings like (what is left of) refine
-> refine'
, and refine the interface a bit before merging.
Mario Carneiro (Jun 19 2024 at 23:05):
also cc: @Damiano Testa , maybe you have some linter ideas in the pipeline that can be expressed as refactors instead / in addition
Damiano Testa (Jun 19 2024 at 23:06):
Yes, the "unnecessary set_option ... in
" linter is a good candidate (and I already wrote a self-corrector for it).
Damiano Testa (Jun 19 2024 at 23:08):
Btw, in the code that I was using, the analogue of your Edit
also had a field for the current syntax, as a check and would perform the substitution only if the current syntax matches the expectation. I could not see this in your code: have I not seen it? Do you think that it would be useful?
Mario Carneiro (Jun 19 2024 at 23:09):
We talked about having some constructors for Edit
, which could do that kind of sanitization. I think that any Edit
returned from runRefactoring
should be considered as real edits, so that the edit application logic doesn't have to do parsing
Damiano Testa (Jun 19 2024 at 23:10):
More generally, any "unnecessary/unused syntax" linter is a good candidate.
Damiano Testa (Jun 19 2024 at 23:10):
Also, if you want to test your refactor tool, the have
vs let
linter has a lot of hits in mathlib.
Damiano Testa (Jun 19 2024 at 23:10):
(although it is silent by default.)
Damiano Testa (Jun 19 2024 at 23:11):
And the multigoal linter would be probably a good challenge!
Mario Carneiro (Jun 19 2024 at 23:11):
is the multigoal linter auto-fixable?
Damiano Testa (Jun 19 2024 at 23:12):
I am not sure that I tried to make it so, but I think that it could be made autofixable. I need to think a little bit about it, though.
Mario Carneiro (Jun 19 2024 at 23:12):
have / let sounds like the easiest one
Damiano Testa (Jun 19 2024 at 23:12):
I suspect that the same tracking logic for metavars that is used in the flexible vs rigid tactics can be used with the multigoal linter. But that would be quite a bit of extra code.
Mario Carneiro (Jun 19 2024 at 23:13):
you can import mathlib in Refactor.Main
if you need
Damiano Testa (Jun 19 2024 at 23:13):
have / let is indeed easier, but not straightforward either if you want to have 100% success rate.
Mario Carneiro (Jun 19 2024 at 23:14):
hopefully the flexible vs rigid linter can share code with the refactoring
Damiano Testa (Jun 19 2024 at 23:15):
Also, the current "unnecessary simpNF
" could be adapted -- at least the case in which there is nolint simpNF
and no more linters are disabled in that call.
Damiano Testa (Jun 19 2024 at 23:17):
Ah, "theorem
with no doc-string to lemma
" should be really straightforward! I'm sure that you love this one...
Damiano Testa (Jun 19 2024 at 23:18):
For this one, there is also no need to worry about long lines: it is a simple translation.
Mario Carneiro (Jun 19 2024 at 23:18):
I'm not sure I like the polarity of that auto-fix
Mario Carneiro (Jun 19 2024 at 23:18):
I'd prefer the theorems get documentation
Damiano Testa (Jun 19 2024 at 23:19):
You mean that refactor
should add doc-strings to undocumented theorem
s? :rofl:
Mario Carneiro (Jun 19 2024 at 23:19):
But I guess you could autofix and then review the changes to undo and document theorems that should have been documented
Mario Carneiro (Jun 19 2024 at 23:20):
or I suppose you could call chat-gpt or something
Damiano Testa (Jun 19 2024 at 23:20):
Anyway, that one is a really straightforward text trasliteration that could be a good test, whether you want to keep the result or not.
Mario Carneiro (Jun 19 2024 at 23:21):
and honestly a pretty cool / nontrivial application of a "refactoring"
Damiano Testa (Jun 19 2024 at 23:21):
That linter should be already ready.
Damiano Testa (Jun 19 2024 at 23:21):
Let me dig up the PR
Damiano Testa (Jun 19 2024 at 23:22):
Damiano Testa (Jun 19 2024 at 23:23):
The conflict is just the alphabetization of the "import all" files.
Damiano Testa (Jun 19 2024 at 23:52):
What about trying it with
def runRefactoring (cmd : TSyntax `command) : CommandElabM (List Edit) := do
unless (cmd.raw.find? (·.isOfKind ``Lean.Parser.Command.docComment)).isNone do return []
match (cmd.raw.find? (·.isOfKind ``Lean.Parser.Command.theorem)) with
| none => return []
| some thm => return [
{ replacement := "lemma"
range := (thm.getHead?.getD default).getRange?.get! }]
Damiano Testa (Jun 19 2024 at 23:52):
(Assuming that I understood correctly the meaning of Edit
.)
Damiano Testa (Jun 19 2024 at 23:54):
Also, I am not sure that I have a use for TSyntax
instead of a plain Syntax
: after all, a plain Syntax
is what you get our of Linter.run
as well.
Damiano Testa (Jun 20 2024 at 00:14):
I tested this: it replaced
import Mathlib.Tactic.Lemma
theorem replaceMe : True := .intro
/-- i have a doc-string -/
theorem doNotReplaceMe : True := .intro
lemma doNotReplaceMeEither : True := .intro
with
import Mathlib.Tactic.Lemma
lemma replaceMe : True := .intro
/-- i have a doc-string -/
theorem doNotReplaceMe : True := .intro
lemma doNotReplaceMeEither : True := .intro
Success!
Damiano Testa (Jun 20 2024 at 01:07):
I left lake exe refactor
running in #13979 on all of Mathlib.
Johan Commelin (Jun 20 2024 at 03:37):
@Mario Carneiro wonderful! I will surely be playing with this a lot!
Siddhartha Gadgil (Jun 20 2024 at 04:53):
Mario Carneiro said:
I'd prefer the theorems get documentation
A reminder that a bunch of machine generated docs are available at https://math.iisc.ac.in/~gadgil/descs/ (and more have been recently generated).
Damiano Testa (Jun 20 2024 at 05:01):
At least one problem with the code above that I posted is that refactor
climbs up in imports, so reaches quickly a file where lemma
is not available. I wonder whether climbing down should have a flag, so that you can guarantee the existence of some syntax.
Damiano Testa (Jun 20 2024 at 06:57):
The "non-documented theorem to lemma" via refactor
was successfully build by CI: #13979.
Mario Carneiro (Jun 20 2024 at 21:17):
Damiano Testa said:
At least one problem with the code above that I posted is that
refactor
climbs up in imports, so reaches quickly a file wherelemma
is not available. I wonder whether climbing down should have a flag, so that you can guarantee the existence of some syntax.
For tests like this, the easy thing to do is to check if the lemma
declaration exists, or a module of interest is imported, and otherwise just return []
Mario Carneiro (Jun 20 2024 at 21:18):
I guess I should add a function which runs before any commands are processed since it might be possible to short-circuit before running the elaborator at all
Damiano Testa (Jun 20 2024 at 21:18):
Yes, this is what I ended up doing in the PR that successfully made the replacement.
Damiano Testa (Jun 20 2024 at 21:19):
In general, though, I still think that it is a fairly common situation where you want to refactor starting from a given module, rather than up to a given module.
Mario Carneiro (Jun 20 2024 at 21:19):
the walking up is a necessity, you should just think of it as running over the whole project
Mario Carneiro (Jun 20 2024 at 21:19):
it doesn't process the files in order
Damiano Testa (Jun 20 2024 at 21:20):
Ok, in any case, the workaround is simple enough.
Mario Carneiro (Jun 20 2024 at 21:23):
but I think what you want is to have that short circuit. There are two possible points at which it could be placed:
- Before running the subprocess: here we know only the name of the module and its direct imports
- After running the subprocess and processing the imports: here we know the module's recursive dependencies and also the list of declarations in the environment
Mario Carneiro (Jun 20 2024 at 21:24):
either one would save you a lot of elaboration cost
Damiano Testa (Jun 20 2024 at 21:25):
I am not too good with performance aspects, but I did find that with the (probably very inefficient) code that I wrote, it took quite a long time to replace all doc-less theorems by lemmas. Granted that it is a lot of operations, but I wonder whether maybe it might be a good test to perform "quickly".
Mario Carneiro (Jun 20 2024 at 21:26):
keep in mind that lake exe refactor
is necessarily lower bounded by the cost of building mathlib
Mario Carneiro (Jun 20 2024 at 21:26):
even if you do nothing at all
Mario Carneiro (Jun 20 2024 at 21:27):
although this is not completely true if you use the aforementioned short-circuit
Damiano Testa (Jun 20 2024 at 21:27):
But it does use olean
s, if they are available? Or not?
Mario Carneiro (Jun 20 2024 at 21:27):
it uses them, but it's still basically equivalent to a full build of every file
Damiano Testa (Jun 20 2024 at 21:28):
Ok, then it was probably not so bad, given that the refactor and the successful build took 1h20 (if I remember correctly).
Damiano Testa (Jun 20 2024 at 21:30):
Btw, is there a notion of just building the syntax trees? I imagine not, due to the very flexible extensibility of the language, but you never know... :smile:
Mario Carneiro (Jun 20 2024 at 21:30):
I've been asking for this for a very long time. This project is the result of deciding that I can't wait on that forever
Damiano Testa (Jun 20 2024 at 21:33):
Is it "theoretically" possible to just parse the syntax though? At least you should parse also syntax extensions, right?
Mario Carneiro (Jun 20 2024 at 21:34):
see also https://leanprover.zulipchat.com/#narrow/stream/270676-lean4/topic/quick.20and.20dirty.20mode
Damiano Testa (Jun 20 2024 at 21:35):
Ok, yes, I have seen these discussions. Let's hope that something happens along these lines!
Damiano Testa (Jun 21 2024 at 07:53):
I think that in the long run, it might make sense to abstract runRefactoring
into an argument to lake exe refactor
, so that we may then do lake exe refactor deprecations
or lake exe refactor unnecessary_syntax
or whatever and each calls refactor
with the specialised runRefactoring
function.
Damiano Testa (Jun 24 2024 at 22:45):
What would be the view of having certain "self-correcting" linters run on a schedule using refactor
? For instance, a good candidate for this could be the "unused tactic" linter, flagging tactics that do not change any metavariable (note that this is different from the unreachable tactic linter). E.g., when I ran it several weeks ago it flagged a few congr
s, some norm_num
s maybe for which the list of metavariables before and after the tactic was unchanged.
The linter could produce the metaprogram that feeds into refactor and then refactor could perform the replacements.
Damiano Testa (Jun 24 2024 at 22:49):
In order to maximize self-referentiality, the linter could write a metaprogram that instructs refactor
to modify runRefactoring
inside its own file, in such a way that a second run of refactor
would perform the removal of unused tactics.
Mario Carneiro (Jun 25 2024 at 00:31):
I don't think there is any need for self modifying code here (except insofar as any refactor is modifying code). The original idea was to have a small library of named refactors which can be selected either at the command line as you suggest, or via some easily editable script (i.e. runRefactoring
is a one liner which calls a previously prepared refactoring function). For "unused tactic", we can just permanently have such a refactoring available but not called by default. There is no need for a linter to write the refactoring, they just share a common bit of code.
Last updated: May 02 2025 at 03:31 UTC