Zulip Chat Archive
Stream: lean4
Topic: Typeclass timeout and outParam
Tomas Skrivan (Dec 19 2021 at 00:43):
I'm getting an odd timeout on typeclass resolution, it is a mix of addition on product type and usage of outParam
. I don't really understand what is going on here, especially I do not understand what exactly outParam
is doing but it is necessary here to allow the nice and simple notation ⟪x,y⟫
class SemiInner (X : Type u) (R : outParam $ Type v) where
semiInner : X → X → R
notation "⟪" x ", " y "⟫" => SemiInner.semiInner x y
-- This is here to demonstrate the need of `outParam` in the `R` parameter
class SemiHilbert (X R) [Add R] extends Add X, SemiInner X R where
add_inner : ∀ x y z : X, ⟪x + y, z⟫ = ⟪x, z⟫ + ⟪y, z⟫
instance (X Y R) [Add R] [SemiInner X R] [SemiInner Y R] : SemiInner (X × Y) R :=
{
semiInner := λ (x,y) (x',y') => ⟪x,x'⟫ + ⟪y,y'⟫
}
class AddSemigroup (A : Type u) extends Add A where
add_assoc (a b c : A) : (a + b) + c = a + (b + c)
instance [Add α] [Add β] : Add (α × β) := ⟨λ p q => (p.1 + q.1, p.2 + q.2)⟩
-- This works
example (X Y R) [Add R] [SemiInner X R] [SemiInner Y R] : SemiInner (X × Y) R := by infer_instance
-- This does not ... why?
set_option trace.Meta.synthInstance true in
example (X Y R) [AddSemigroup R] [SemiInner X R] [SemiInner Y R] : SemiInner (X × Y) R:= by infer_instance
How can I change the setup such that the last class inference works but I can still use the notation ⟪x,y⟫
without specifying the type R
?
Mac (Dec 19 2021 at 01:08):
@Tomas Skrivan ypur code gives me an "unknown metavariable '?_uniq.1543'" error, which feels like a Lean bug to me.
Tomas Skrivan (Dec 19 2021 at 01:15):
Which version of are you running? Mine is nightly-12-15.
Tomas Skrivan (Dec 19 2021 at 01:20):
For me it gets stuck at an infinite loop, synthesizing instances of Add (USize × ... × USize)
:
[Meta.synthInstance.resume] size: 1, Add (USize × ?m.1642) <== Add USize
[Meta.synthInstance.resume] size: 2, Add (USize × USize) <== Add USize
[Meta.synthInstance.newAnswer] size: 2, Add (USize × USize)
[Meta.synthInstance.newAnswer] val: instAddProd
[Meta.synthInstance.resume] size: 4, Add (USize × USize × USize) <== Add (USize × USize)
[Meta.synthInstance.newAnswer] size: 4, Add (USize × USize × USize)
[Meta.synthInstance.newAnswer] val: instAddProd
[Meta.synthInstance.resume] size: 6, Add (USize × USize × USize × USize) <== Add (USize × USize × USize)
[Meta.synthInstance.newAnswer] size: 6, Add (USize × USize × USize × USize)
[Meta.synthInstance.newAnswer] val: instAddProd
[Meta.synthInstance.resume] size: 8, Add
(USize × USize × USize × USize × USize) <== Add (USize × USize × USize × USize)
...
Mac (Dec 19 2021 at 01:27):
@Tomas Skrivan I'm on Lean (version 4.0.0-nightly-2021-12-17, commit e65f3fe81032, Release)
.
Tomas Skrivan (Dec 19 2021 at 01:27):
Hmm, now I'm getting the unknown metavariable too. Not sure what changed but the trace still shows the infinite loop of synthesizing longer and longer versions of Add (USize × ... × USize)
Tomas Skrivan (Dec 19 2021 at 01:29):
I have tried nigtly 11-12, 12-15, 12-19 and all give the same error.
Tomas Skrivan (Dec 19 2021 at 01:32):
You get the timeout error when you turn of the tracing.
Mac (Dec 19 2021 at 04:28):
@Tomas Skrivan after looking at the trace, I figured out the problem is that the Add R
instance comes first, when it should come last in the the parameter list. That is, the following modified mwe works:
class SemiInner (X : Type u) (R : outParam $ Type v) where
semiInner : X → X → R
notation "⟪" x ", " y "⟫" => SemiInner.semiInner x y
class SemiHilbert (X R) [Add R] extends Add X, SemiInner X R where
add_inner : ∀ x y z : X, ⟪x + y, z⟫ = ⟪x, z⟫ + ⟪y, z⟫
-- The `Add R` being at the end here is important
instance (X Y R) [SemiInner X R] [SemiInner Y R] [Add R] : SemiInner (X × Y) R where
semiInner := λ (x,y) (x',y') => ⟪x,x'⟫ + ⟪y,y'⟫
class AddSemigroup (A : Type u) extends Add A where
add_assoc (a b c : A) : (a + b) + c = a + (b + c)
instance [Add α] [Add β] : Add (α × β) := ⟨λ p q => (p.1 + q.1, p.2 + q.2)⟩
-- This works
example (X Y R) [SemiInner X R] [SemiInner Y R] [Add R] : SemiInner (X × Y) R := inferInstance
-- This works too now
example (X Y R) [SemiInner X R] [SemiInner Y R] [AddSemigroup R] : SemiInner (X × Y) R := inferInstance
The problem is that add instance synthesis is defaulting R
to something other than the natural outParam
result which is then causing problems in synthesis.
Mac (Dec 19 2021 at 04:33):
Instance synthesis works in parameter order (e.g., from left to right). If the outParam
class comes first, it fixes R
for the coming Add
synthesis (as desired). If Add R
comes first instead, it may try to choose an R
itself for future synthesis (which you do not want).
Tomas Skrivan (Dec 19 2021 at 08:04):
Thanks a lot! I have encountered this problem before, where I had to reorder the typeclass parameters, but didn't realize that is the problem here again.
Would be nice to detect a dangerous order automatically and give a warning.
Notification Bot (Dec 19 2021 at 12:03):
Tomas Skrivan has marked this topic as unresolved.
Tomas Skrivan (Dec 19 2021 at 12:06):
I'm having more issues, I also want to define product on SemiHilbert
there you cannot move Add R
after SemiHilbert
arguments. I'm getting timeout error again:
class SemiInner (X : Type u) (R : outParam $ Type v) where
semiInner : X → X → R
notation "⟪" x ", " y "⟫" => SemiInner.semiInner x y
-- The `Add R` being at the end here is important
instance (X Y R) [SemiInner X R] [SemiInner Y R] [Add R] : SemiInner (X × Y) R where
semiInner := λ (x,y) (x',y') => ⟪x,x'⟫ + ⟪y,y'⟫
class AddSemigroup (A : Type u) extends Add A where
add_assoc (a b c : A) : (a + b) + c = a + (b + c)
instance [Add α] [Add β] : Add (α × β) := ⟨λ p q => (p.1 + q.1, p.2 + q.2)⟩
class SemiHilbert (X) (R : outParam $ Type v) [outParam $ Add R] extends Add X, SemiInner X R where
add_inner : ∀ x y z : X, ⟪x + y, z⟫ = ⟪x, z⟫ + ⟪y, z⟫
instance (X Y R) [Add R] [SemiHilbert X R] [SemiHilbert Y R] : SemiHilbert (X × Y) R :=
{
add_inner := sorry
}
-- Here I'm getting timeout again
example (X Y R) [AddSemigroup R] [SemiHilbert X R] [SemiHilbert Y R] : SemiHilbert (X × Y) R := inferInstance
It does not happen when R
is not marked as outParam
in the definition of SemiHilbert
, but I want it there as I often want R
to be automatically inferred.
Tomas Skrivan (Dec 19 2021 at 12:41):
The reason why I need the outParam
under the R
parameter in SemiHilbert
is that I then want to define class Prop HasAdjoint
class HasAdjoint {X Y} (f : X → Y) {R} [Add R] [SemiHilbert X R] [SemiHilbert Y R] : Prop where
hasAdjoint : ∃ (f' : Y → X), ∀ x y, ⟪f' y, x⟫ = ⟪y, f x⟫
variable (X Y R) [Add R] [SemiHilbert X R] [SemiHilbert Y R]
variable (f : X → Y) [HasAdjoint f]
#check f
and without the outParam
the [HasAdjoint f]
can't figure out what R
is.
Mac (Dec 19 2021 at 17:41):
@Tomas Skrivan here is a working example:
class SemiInner (X : Type u) (R : outParam $ Type v) where
semiInner : X → X → R
notation "⟪" x ", " y "⟫" => SemiInner.semiInner x y
-- The `Add R` being at the end here is important
instance (X Y R) [SemiInner X R] [SemiInner Y R] [Add R] : SemiInner (X × Y) R where
semiInner := λ (x,y) (x',y') => ⟪x,x'⟫ + ⟪y,y'⟫
-- We set this to priority low so every other `Add` instance is tried first
instance (priority := low) [Add α] [Add β] : Add (α × β) := ⟨λ p q => (p.1 + q.1, p.2 + q.2)⟩
class SemiHilbert (X) (R : outParam $ Type v) [outParam $ Add R] extends Add X, SemiInner X R where
add_inner : ∀ x y z : X, ⟪x + y, z⟫ = ⟪x, z⟫ + ⟪y, z⟫
-- Same here
instance (priority := low) (X Y R) [Add R] [SemiHilbert X R] [SemiHilbert Y R] : SemiHilbert (X × Y) R where
add_inner := sorry
-- This comes after `SemiHilbert` because we want to try the `AddSemogroup.toAdd` instance before the `SemiHilbert.toAdd` one
class AddSemigroup (A : Type u) extends Add A where
add_assoc (a b c : A) : (a + b) + c = a + (b + c)
-- This now works
example (X Y R) [AddSemigroup R] [SemiHilbert X R] [SemiHilbert Y R] : SemiHilbert (X × Y) R := inferInstance
class HasAdjoint {X Y} (f : X → Y) {R} [Add R] [SemiHilbert X R] [SemiHilbert Y R] : Prop where
hasAdjoint : ∃ (f' : Y → X), ∀ x y, ⟪f' y, x⟫ = ⟪y, f x⟫
variable (X Y R) [Add R] [SemiHilbert X R] [SemiHilbert Y R]
variable (f : X → Y) [HasAdjoint f]
#check f
Mac (Dec 19 2021 at 17:51):
However, the man problem here that I find suspicious is that Lean's typeclass synthesis doesn't try to synthesize SemiHilbert (X × Y) R
(i.e., the desired instance) first, but instead immediately tries SemiHilbert (X × Y) ?m.1657
(i.e., with an metavariable output -- a produce of the outParam
) which is a major cause of all this trouble.
I feel like it should try to synthesize SemiHilbert (X × Y) R
first and only when that fails try SemiHilbert (X × Y) ?m.1657
, but that may just be an uniformed opinion.
Reid Barton (Dec 19 2021 at 18:40):
[outParam $ Add R]
seems to express two contradictory intents: one that the argument should be synthesized by TC instance search, the other that it should be determined by the SemiHilbert
instance.
Reid Barton (Dec 19 2021 at 18:41):
I wonder whether this interaction of features has been properly thought through (certainly it hasn't been by me).
Reid Barton (Dec 19 2021 at 18:42):
I would be inclined to try {}
binders for the Add R
in the SemiHilbert
class and instances.
Mac (Dec 19 2021 at 19:15):
@Reid Barton but [outParam $ Add R]
wasn't the problem here?
Reid Barton (Dec 19 2021 at 19:15):
I think it is the core issue though--you don't want to try synthesizing Add R
ever
Reid Barton (Dec 19 2021 at 19:16):
Instead, you want to learn it from the SemiHilbert
instance
Mac (Dec 19 2021 at 19:33):
@Reid Barton true, an alternative way to structure this would be something like:
class SemiHilbert (X) (R : outParam $ Type v) extends SemiInner X R where
add_X : X → X → X
add_R : R → R → R
add_inner : ∀ x y z : X, ⟪add_X x y, z⟫ = add_R ⟪x, z⟫ ⟪y, z⟫
instance (X R) [SemiHilbert X R] : Add X := ⟨SemiHilbert.add_X⟩
instance (X R) [SemiHilbert X R] : Add R := ⟨SemiHilbert.add_R (X := X)⟩
instance (priority := low) (X Y R) [SemiHilbert X R] [SemiHilbert Y R] : SemiHilbert (X × Y) R where
add_X := Add.add
add_R := Add.add
add_inner := sorry
class AddSemigroup (A : Type u) extends Add A where
add_assoc (a b c : A) : (a + b) + c = a + (b + c)
example (X Y R) [SemiHilbert X R] [SemiHilbert Y R] [AddSemigroup R] : SemiHilbert (X × Y) R := inferInstance
Reid Barton (Dec 19 2021 at 19:45):
This looks awkward though because in the actual instances, you probably want to use existing Add
instances for real
etc.
Reid Barton (Dec 19 2021 at 19:46):
And
instance (X R) [SemiHilbert X R] : Add R
looks very dubious as there is no way to determine X
Mac (Dec 19 2021 at 20:41):
@Reid Barton very true, there is not really a silver bullet here, because Lean does not, afaik, provide a neat way to force Add R
use the instance and R
that come from SemiHilbert
. [outParam $ Add R]
is closest approximation, but it has the problems you mentioned.
Tomas Skrivan (Dec 19 2021 at 21:55):
Here is my solution using utility classes Trait X
and Trait₂ X Y
that fetch R
based on X
or X Y
and there is an instance of Trait
whenever there is an instance of SemiInner
.
class SemiInner (X : Type u) (R : outParam $ Type v) where
semiInner : X → X → R
notation "⟪" x ", " y "⟫" => SemiInner.semiInner x y
class Trait (X : Type u) where
R : Type v
class Trait₂ (X : Type u) (Y : Type v) where
R : Type w
attribute [reducible] Trait.R Trait₂.R
@[reducible] instance (X) (R : outParam $ Type v) [SemiInner X R] : Trait X := ⟨R⟩
@[reducible] instance (X Y) [Trait X] : Trait₂ X Y := ⟨Trait.R X⟩
@[reducible] instance (X Y) [Trait Y] : Trait₂ X Y := ⟨Trait.R Y⟩
-- The `Add R` being at the end here is important
instance (X Y R) [SemiInner X R] [SemiInner Y R] [Add R] : SemiInner (X × Y) R where
semiInner := λ (x,y) (x',y') => ⟪x,x'⟫ + ⟪y,y'⟫
class AddSemigroup (A : Type u) extends Add A where
add_assoc (a b c : A) : (a + b) + c = a + (b + c)
instance [Add α] [Add β] : Add (α × β) := ⟨λ p q => (p.1 + q.1, p.2 + q.2)⟩
class SemiHilbert (X) (R : outParam $ Type v) [outParam $ Add R] extends SemiInner X R, Add X where
add_inner : ∀ x y z : X, ⟪x + y, z⟫ = ⟪x, z⟫ + ⟪y, z⟫
-- The important bit here is that `R` is fetched with the utility class Trait and not spelled out explicitely
instance (X Y) [Trait₂ X Y] [Add (Trait₂.R X Y)]
[SemiHilbert X (Trait₂.R X Y)]
[SemiHilbert Y (Trait₂.R X Y)]
: SemiHilbert (X × Y) (Trait₂.R X Y) :=
{
add_inner := sorry
}
example (X Y R) [Add R] [SemiHilbert X R] [SemiHilbert Y R] : SemiHilbert (X × Y) R := inferInstance
example (X Y R) [AddSemigroup R] [SemiHilbert X R] [SemiHilbert Y R] : SemiHilbert (X × Y) R := inferInstance
class HasAdjoint {X Y} (f : X → Y) {R} [Add R] [SemiHilbert X R] [SemiHilbert Y R] : Prop where
hasAdjoint : ∃ (f' : Y → X), ∀ x y, ⟪f' y, x⟫ = ⟪y, f x⟫
variable (X Y R) [Add R] [SemiHilbert X R] [shy : SemiHilbert Y R] (x x' : X) (r : R)
variable (f : X → Y) [HasAdjoint f] (x x' : X) (r : Trait₂.R X Y)
#check ⟪x, x'⟫ + r -- This works because `Trait₂.R` is reducible
Tomas Skrivan (Dec 19 2021 at 21:57):
Mac said:
Tomas Skrivan here is a working example:
Nice! I was fiddling with priorities quite a bit and was unable to get it working.
Tomas Skrivan (Dec 19 2021 at 23:56):
Arghhh, the approach with Trait
is also problematic. The SemiHilbert
instance on product type causes a timeout error on
example {α β} : Add (α × β) := by infer_instance
As it does not fail properly it completely messes up other type class inferences.
Tomas Skrivan (Dec 20 2021 at 00:26):
Adding [Trait₂ X Y]
at the beginning of the instance of SemiHilbert (X × Y) R
seems to solve the above problem. The value [Trait₂ X Y]
is not used at all, but is there just to safe guard a misuse of instance.
instance (X Y R) [Trait₂ X Y] [Add R] [SemiHilbert X R] [SemiHilbert Y R]
: SemiHilbert (X × Y) R :=
{
add_inner := sorry
}
Last updated: Dec 20 2023 at 11:08 UTC