Zulip Chat Archive
Stream: lean4
Topic: projection instance binderinfo problem
Jovan Gerbscheid (Mar 15 2025 at 15:48):
There is something wrong with either the synthesization order check, or the projection instance binder info. In lean#5376 a change was made to the projection instance binder info to be implicit.
But in the following, if I try to define an instance exactly like the projection instance, Lean complains
class Nontrivial (α : Type) : Prop where
class Group (α : Type) where
class IsSimpleGroup (G : Type) [Group G] : Prop extends Nontrivial G where
/-
IsSimpleGroup.toNontrivial {G : Type} {inst✝ : Group G} [self : IsSimpleGroup G] : Nontrivial G
-/
#check IsSimpleGroup.toNontrivial
/-
cannot find synthesization order for instance @instNontrivialOfIsSimpleGroup with type
∀ {G : Type} {x : Group G} [self : IsSimpleGroup G], Nontrivial G
all remaining arguments have metavariables:
@IsSimpleGroup G ?x✝
-/
instance {G : Type} {_ : Group G} [self : IsSimpleGroup G] : Nontrivial G := sorry
Aaron Liu (Mar 15 2025 at 15:49):
The Group G
argument is not determined exactly.
Aaron Liu (Mar 15 2025 at 15:49):
Since it is implicit now, it won't be solved for by typeclass.
Jovan Gerbscheid (Mar 15 2025 at 15:50):
I agree, and this means that the automatically generated IsSimpleGroup.toNontrivial
is problematic.
Kyle Miller (Mar 15 2025 at 15:56):
I just noticed this issue last week, and I have some ideas for fixes that I hope will land by the end of the quarter.
Kyle Miller (Mar 15 2025 at 15:56):
It came up when trying to give all parent instances (even non-subobject parents) the same implicit argument rules for the type's parameters. These are basically user-defined instances.
Kyle Miller (Mar 15 2025 at 15:57):
Note that IsSimpleGroup.toNontrivial
is not problematic since the synth order algorithm has special support for direct projections.
Kyle Miller (Mar 15 2025 at 15:59):
Edit: no, I read too quickly and misremembered the current algorithm, it's problematic.
Kyle Miller (Mar 15 2025 at 16:06):
I haven't run the proposal by anyone yet, since I wanted to test it first and see if it causes a performance impact, but here it is for your consideration:
The rough idea:
- The synth order algorithm will see every implicit argument that's an instance as a true instance implicit argument. This allows projections to be used as instances, without needing to special case projections in the algorithm. Rationale: With projections, you don't want to resynthesize the type's parameters, since they're all determined by the
self
argument and should be solved for by unification. Rationale: non-subobject parents are not true projections, but they should be handled in the same way. - If an instance argument has "mixin arguments" (which I am loosely defining as any instance arguments that appear anywhere in its type), then those mixin arguments will be synthesized after that argument. Heuristic: instances are supposed to be canonical, and the observation of lean#5376 was that most of the time they seem to be solved for by unification anyway. Heuristic: the argument's mixins give much less information about whether the instance will succeed than the argument itself.
Jovan Gerbscheid (Mar 15 2025 at 22:12):
I like this idea. How do you plan to deal with the more awkward cases where a "mixin argument" can't be determined by unification? For example in
NoMaxOrder.infinite {α : Type*} [Preorder α] [Nonempty α] [NoMaxOrder α] : Infinite α
The parameter Preorder α
appears in the parameter NoMaxOrder α
, but only as a LT
argument. This is annoying because it means that to failing to synthesize Infinite _
(or Nontrivial _
, Nonempty _
) requires traversing the entire Preorder
hierarchy.
Jovan Gerbscheid (Mar 15 2025 at 22:18):
I think what could happen is:
We first synthesize NoMaxOrder α
, and either this gives us as LT
instance based on a Preorder
instance, in which case we are happy because unification gives us the result.
Or, unification gets stuck, which forces unification to resolve the stuck metavariable of type Preorder α
using a nested type class search.
Jovan Gerbscheid (Mar 15 2025 at 22:52):
I also like that the synth order treats implicit/instance implicit binders the same. In the status quo, there are a bunch of instances that can be optimized by replacing the [..]
binder by {_ : ..}
, but that isn't nice to write or look at. I presume that the projection instance binder annotations can then go back to what they were before lean#5376, because the difference doesn't really matter anymore.
Kyle Miller (Mar 15 2025 at 23:15):
For this example, the proposed algorithm would say the synth order is Nonempty α
, NoMaxOrder α
, and then Preorder α
. Suppose we've found a Nonempty
instance. My rough understanding then is:
- If there's obviously no
NoMaxOrder
that can apply, we are done. - Otherwise, if there is a possible
NoMaxOrder
, it will try unifying the LT arguments. In this case it's@Preorder.toLT α ?preorderInst
. Unifying against that is indeed a case of a stuck metavariable. However, class projections are special, and this does not trigger a nested type class search (see docs#Lean.Meta.getStuckMVar?). - It then goes on to try to synthesize the
Preorder α
instance.
Jovan Gerbscheid (Mar 16 2025 at 23:06):
Actually looking at docs#Lean.Meat.getStuckMVar?, I see that it specifically does trigger a nested type class search "if e
is a type class projection that is stuck because the instance has not been synthesized yet."
Kyle Miller (Mar 16 2025 at 23:30):
Huh, you're right, I read it backwards. Earlier I tried making a example from NoMaxOrder.infinite
that would trigger a nested type class search but I was having some trouble.
Would you be interested in trying to verify whether it does or doesn't actually trigger a nested typeclass search with this example? You may need to turn off the synthOrder error and then override the synth order. I think it should be possible to do without modifying Lean; just some attributes and a set_option
.
Jovan Gerbscheid (Mar 17 2025 at 11:02):
Yes, in this example setting the synthOrder to be #[2,3] does indeed trigger a nested type class search
import Mathlib
/-!
# Specify the synthesization order for instances
-/
namespace Lean.Meta
/-- Specify the synthesization order for instances. -/
def Instances.setSynthOrder (declName : Name) (synthOrder : Array Nat) : CoreM Unit := do
let some entry := Meta.instanceExtension.getState (← getEnv) |>.instanceNames.find? declName |
throwError "'{declName}' does not have [instance] attribute"
instanceExtension.add { entry with synthOrder } entry.attrKind
end Lean.Meta
namespace Lean.Elab.Command
open Meta
/-- Specify the synthesization order for instances. -/
elab "set_synth_order " name:ident synthOrder:term : command => do
let q := Term.MatchExpr.toDoubleQuotedName name
elabCommand (← `(command| run_meta Instances.setSynthOrder $q $synthOrder))
end Lean.Elab.Command
attribute [instance high] NoMaxOrder.infinite
set_synth_order NoMaxOrder.infinite #[2,3]
variable (α : Type) [Preorder α] [Nonempty α] [NoMaxOrder α]
set_option trace.Meta.synthInstance true
#synth Infinite α
/-
[Meta.synthInstance] ✅️ Infinite α ▼
[] new goal Infinite α ▶
[] ✅️ apply @NoMaxOrder.infinite to Infinite α ▶
[] ✅️ apply inst✝¹ to Nonempty α ▶
[resume] propagating Nonempty α to subgoal Nonempty α of Infinite α ▶
[] ✅️ apply inst✝ to NoMaxOrder α ▼
[tryResolve] ✅️ NoMaxOrder α ≟ NoMaxOrder α ▼
[] ✅️ Preorder α ▼
[] new goal Preorder α ▶
[] ✅️ apply inst✝² to Preorder α ▶
[] result inst✝²
[answer] ✅️ NoMaxOrder α
[] ✅️ Preorder α ▶
[resume] propagating NoMaxOrder α to subgoal NoMaxOrder α of Infinite α ▶
[] result NoMaxOrder.infinite
-/
Jovan Gerbscheid (Mar 17 2025 at 11:15):
Looking more closely, I am confused why the nested type class search is triggered
[isDefEq] ✅️ @NoMaxOrder α (@Preorder.toLT α ?m.52464) =?= @NoMaxOrder α (@Preorder.toLT α inst✝²) ▼
[] ✅️ α =?= α
[synthInstance] ✅️ Preorder α ▶
[] ✅️ @Preorder.toLT α inst✝² =?= @Preorder.toLT α inst✝² ▶
Because this unification problem is perfectly solvable with simple unification.
Jovan Gerbscheid (Mar 17 2025 at 11:17):
So I think that a fix to the unification algorithm is required, so that it only tries nested type class search when this is actually necessary.
Jovan Gerbscheid (Mar 17 2025 at 19:46):
Another bad instance that was recently added is
CategoryTheory.HasCardinalFilteredColimits.hasColimitsOfShape.{w, v₁, u₁} {C : Type u₁} {inst✝ : Category.{v₁, u₁} C}
(κ : Cardinal.{w}) {inst✝¹ : Fact κ.IsRegular} [self : HasCardinalFilteredColimits C κ] (J : Type w) [SmallCategory J]
[IsCardinalFiltered J κ] : Limits.HasColimitsOfShape J C
Where lean should complain that there is no way of determining κ
. But I guess it doesn't complain because this function is a class projection.
Last updated: May 02 2025 at 03:31 UTC