Open fpvandoorn opened 1 year ago
It seems 2. is the obvious option here, if it can be done in a low-impact way. It would be good to have a PR that we can run Mathlib against to measure the performance effect.
I think 3. is too bossy to users, and 4. is too unpredictable.
I think 2 is also the best option.
I started trying to manually change the priorities in mathlib as a preparation (i.e. with a script) - and to see how big the effects are on elaboration time. Changing instance priorities causes many things to break in the library, and I stopped, because of a lack of time.
What might be a good idea is to make a branch where we delete 75-95% of mathlib and test changes on the remainder. That way we can get reasonable estimates about performance, but not have to fix all files in mathlib that break because of the instance changes.
Once I have more time for Lean, I'm willing to do that, but of course anyone should feel free to do this earlier.
Whenever a class (ABCD) extends 2+ classes (AB and AC) that share a common ancestor (A), then the priority of AB.toA should be higher than that of AC.toA.
What happens if user later adds?
class ABCE (α : Type _) extends AC α, AB α where
e : Nat
Automatically satisfying the coherence rule (option 4) is probably not feasible, since you can indeed write structures that conflict with each other.
Automatically satisfying the coherence rule (option 4) is probably not feasible, since you can indeed write structures that conflict with each other.
This is my point. Solution 2 will generate counter intuitive behavior because we will have conflicts.
What is counterintuitive about "extends
uses priority 100"? Probably, I got used to Mathlib convention too much and don't see why it's counterintuitive.
Background
Type-class inference is a large part of the Mathlib's compilation time (roughly 50%). I think it can be greatly improved using better instance priorities. A preliminary test changing only 2 instances in Mathlib resulted in a 29% decrease in peak memory usage.
In mathlib3 we used the following heuristics:
instance [CommGroup α] : Group α
or[Algebra R A] : Module R A
) should have a lower-than-default priority. This is usually the case if all types in the conclusion are (distinct) variables, and this is sometimes called forgetful inheritance. The reason is that we first want to try instances that are specific to this class (we want to findadd_group ℤ
before trying alladd_comm_group
instances). We typically used priority 100 for this, but based on human judgement we sometimes used a priority that is a bit higher or lower[Group G] [Group H] : Group (G × H)
. These instances have (approximately) the default priority of 1000.Current Mathlib4 status
Currently, all instances generated by
class ... extends ...
have priority 1000 (the default, which until recently we couldn't change). Other than that we mostly follow mathlib3's priorities.Mathlib4 heavily uses diamonds when extending classes (i.e. extending 2 or more classes that share a field). Here is an example of a diamond:
My understanding is that whenever we have a diamond and we write a projection we strongly prefer to use the first parent for each structure (we want to use
ABCD.toAB
). These functions are just projections, so let's call these parents "projection parents". The functionABCD.toAC
involves unpacking and re-packing the structure, and is therefore worse to use. Let's call these "auxiliary parents". Note however that the above instance search produces the suboptimal projectionABCD.toAC
.Note that this is not solved by changing the priorities of
ABCD.toAB
orABCD.toAC
: we need to change the instance priorities ofAB.toA
orAC.toA
. So this is not a local problem: we need some global coherence of instance priorities. I think we want to have the following coherence rule for instance priorities:Whenever a class (
ABCD
) extends 2+ classes (AB
andAC
) that share a common ancestor (A
), then the priority ofAB.toA
should be higher than that ofAC.toA
.Ideally, we also have the following two refinements:
ABCD
extends more than 2 classes this condition should hold for every pair of(AB,AC)
whereAB
is a projection parent andAC
is a auxiliary parent.A
is not a direct parent ofAB
orAC
, but any ancestor. The rule should be that the path with highest priority fromA
toABCD
goes viaAB
.Possible solutions
Here are some possible solutions, going from least changes in Lean core to most changes in Lean core
class ACB (α : Type _) extends AC α, AB α
conflicts withABCD
above), and in this case the only proper solution is to reorder the arguments of theextends
command.Related Zulip discussion