Zulip Chat Archive
Stream: lean4
Topic: Constraint Inference bug (?)
Daniel Rogozin (Mar 12 2023 at 18:34):
Hello everyone
I've just encountered a weird phenomenon, which seems to be a bug in constraint inference
I have the following typeclasses (methods are omitted for simplicity)
class CurveGroup {F : Type _} [Field F] (C : Curve F) (K : outParam $ Type _) where
class PointSerialise {F : Type _} [Field F] (C : Curve F) (K : outParam $ Type _) [CurveGroup C K] where
and also I have instances for types ProjectivePoint
and AffinePoint
(with required typevariables and constraints defined above in my module)
instance : CurveGroup C (ProjectivePoint C) where
instance : CurveGroup C (AffinePoint C) where
But when I'm trying to declare instances of PointSerialise
in the same module, instance : PointSerialise C (ProjectivePoint C) where
is accepted, whereas instance : PointSerialise C (AffinePoint C) where
is rejected with failed to synthesize instance
CurveGroup C (AffinePoint C)
. That's strange 'cause all required CurveGroup
instances are declared in the same module.
Kevin Buzzard (Mar 12 2023 at 18:37):
Can you post a #mwe ? Your question is much more likely to get a helpful answer that way. Right now all we know is that either you made a mistake somewhere or there's a bug, and it's difficult to diagnose further without being able to see the error on my own computer.
Daniel Rogozin (Mar 12 2023 at 18:41):
I've just pushed the commit with that error, I can post the github link here
Daniel Rogozin (Mar 12 2023 at 18:42):
Or let me just post the full listing with required definitions
Daniel Rogozin (Mar 12 2023 at 18:48):
import YatimaStdLib.Ring
structure Curve (F : Type _) [Field F] where
a : F
b : F
structure ProjectivePoint {F : Type _} [Field F] (C : Curve F) where
X : F
Y : F
Z : F
inductive AffinePoint {F : Type _} [Field F] (C : Curve F) where
| affine (X : F) (Y : F) : AffinePoint C
| infinity : AffinePoint C
deriving BEq
variable {F : Type _} [Field F] (C : Curve F)
class CurveGroup {F : Type _} [Field F] (C : Curve F) (K : outParam $ Type _) where
zero : K
inv : K → K
add : K → K → K
double : K → K
open ProjectivePoint in
instance : CurveGroup C (ProjectivePoint C) where
zero := infinity
inv := fun ⟨x, y, z⟩ => ⟨x, 0 - y, z⟩
add := add
double := double
open AffinePoint in
instance : CurveGroup C (AffinePoint C) where
zero := infinity
inv := neg
add := add
double := double
class PointSerialise {F : Type _} [Field F] (C : Curve F) (K : outParam $ Type _) [CurveGroup C K] where
serialise : K → ByteArray
deserialise : ByteArray → Option K
namespace Serialise
variable {F : Type _} [Field F] {C : Curve F}
-- rejected with "failed to synthesize instance CurveGroup C (AffinePoint C)"
instance : PointSerialise C (AffinePoint C) where
serialise := sorry
deserialise := sorry
-- accepted
instance : PointSerialise C (ProjectivePoint C) where
serialise := sorry
deserialise := sorry
Daniel Rogozin (Mar 12 2023 at 18:49):
Here it is
Daniel Rogozin (Mar 12 2023 at 18:49):
The full module is in here
https://github.com/yatima-inc/FF.lean/blob/daniel/restructure-typeclasses/FF/EllipticCurve.lean
Kevin Buzzard (Mar 12 2023 at 19:18):
this isn't a #mwe because it has a random import :-/
Kevin Buzzard (Mar 12 2023 at 19:20):
This is a mwe:
import Mathlib
structure Curve (F : Type _) [Field F] where
a : F
b : F
structure ProjectivePoint {F : Type _} [Field F] (C : Curve F) where
X : F
Y : F
Z : F
inductive AffinePoint {F : Type _} [Field F] (C : Curve F) where
| affine (X : F) (Y : F) : AffinePoint C
| infinity : AffinePoint C
deriving BEq
variable {F : Type _} [Field F] (C : Curve F)
class CurveGroup {F : Type _} [Field F] (C : Curve F) (K : outParam $ Type _) where
open ProjectivePoint in
instance : CurveGroup C (ProjectivePoint C) where
open AffinePoint in
instance : CurveGroup C (AffinePoint C) where
class PointSerialise {F : Type _} [Field F] (C : Curve F) (K : outParam $ Type _) [CurveGroup C K] where
serialise : K → ByteArray
deserialise : ByteArray → Option K
namespace Serialise
variable {F : Type _} [Field F] {C : Curve F}
-- rejected with "failed to synthesize instance CurveGroup C (AffinePoint C)"
instance : PointSerialise C (AffinePoint C) where
serialise := sorry
deserialise := sorry
-- accepted
instance : PointSerialise C (ProjectivePoint C) where
serialise := sorry
deserialise := sorry
Henrik Böving (Mar 12 2023 at 19:22):
I'm not sure whether importing mathlib counts as more of a mwe compared to importing yatimas lib :P
Daniel Rogozin (Mar 12 2023 at 19:22):
Fair enough, sorry
Kevin Buzzard (Mar 12 2023 at 19:22):
and this is a better one because it doesn't need mathlib. The trick is to get rid of all imports so it works for everyone else and they can see the same error. Note that I'm changing your definitions but keeping the error alive.
class Field (F : Type _) where
structure Curve (F : Type _) [Field F] where
a : F
b : F
structure ProjectivePoint {F : Type _} [Field F] (C : Curve F) where
X : F
Y : F
Z : F
inductive AffinePoint {F : Type _} [Field F] (C : Curve F) where
| affine (X : F) (Y : F) : AffinePoint C
| infinity : AffinePoint C
deriving BEq
variable {F : Type _} [Field F] (C : Curve F)
class CurveGroup {F : Type _} [Field F] (C : Curve F) (K : outParam $ Type _) where
open ProjectivePoint in
instance : CurveGroup C (ProjectivePoint C) where
open AffinePoint in
instance : CurveGroup C (AffinePoint C) where
class PointSerialise {F : Type _} [Field F] (C : Curve F) (K : outParam $ Type _) [CurveGroup C K] where
serialise : K → ByteArray
deserialise : ByteArray → Option K
namespace Serialise
variable {F : Type _} [Field F] {C : Curve F}
-- rejected with "failed to synthesize instance CurveGroup C (AffinePoint C)"
instance : PointSerialise C (AffinePoint C) where
serialise := sorry
deserialise := sorry
-- accepted
instance : PointSerialise C (ProjectivePoint C) where
serialise := sorry
deserialise := sorry
Kevin Buzzard (Mar 12 2023 at 19:24):
Henrik Böving said:
I'm not sure whether importing mathlib counts as more of a mwe compared to importing yatimas lib :P
Well I guess this is a cultural thing: mathlib imports in mwes in lean 3 have always been acceptable. Of course I can see your point (which is why I fixed it) but a lot of people on this chat have got mathlib handy; I suspect rather fewer have got Yatima's library handy. It's all about making it easier for as many other people as possible. Anyway, I removed the mathlib import so maybe you can figure out the problem now :-)
Kevin Buzzard (Mar 12 2023 at 19:26):
[Meta.synthInstance] ❌ CurveGroup C (AffinePoint C) ▼
[] new goal CurveGroup C _tc.0 ▶
[] ✅ apply @instCurveGroupProjectivePoint to CurveGroup C (ProjectivePoint C) ▶
[] result instCurveGroupProjectivePoint C
[] ❌ result type
CurveGroup C (ProjectivePoint C)
is not definitionally equal to
CurveGroup C (AffinePoint C)
Not that instance Lean! What are you doing??
Kevin Buzzard (Mar 12 2023 at 19:28):
Is the issue that the OutParam
is being used incorrectly? I've never understood these properly, but my basic understanding is that class CurveGroup {F : Type _} [Field F] (C : Curve F) (K : outParam $ Type _) where
means "you give me F and C and I'm going to figure out what K is later on, and there is going to be one answer so you are going to suffer if you want there to be two". Don't take my word for it here though.
Henrik Böving (Mar 12 2023 at 19:36):
Yes that is the reason. Basically when you have parameters p_1, p_2, ... p_n and you declared p_{n+1} as out param what you say is that p_1...p_n uniquely determine p_{n+1} so you do not need it at the beginning of type class synthesis since the first instance you find that matches should give you a value for p_{n+1}. Since in this case this invariant does (obviously) not hold things blow up.
Henrik Böving (Mar 12 2023 at 19:37):
If you think of type class instances as a relational database you might also say that the non output parameters form a composite primary key for the outParam ones.
Daniel Rogozin (Mar 12 2023 at 19:45):
Thanks! I think outParam
was redundant. The error is no longer relevant after removing outParam
.
Henrik Böving (Mar 12 2023 at 19:49):
Well it depends really. outParam's can be helpful mostly when type inference isn't strong enough. For example say that you have a HAdd
type class with 3 parameters (2 for the addition input, 1 for the addition output). One can argue whether the output one should be an outParam
or not. If you can safely rely on type inference managing to infere a value for the third one all the time you can safely let it be a normal parameter and grant your users more freedom in the instances they might declare. If you think this is not the case you might want to declare it an outParam which takes a bit of freedom away from the instances you can write (as you have just experienced yourself) but can make the typeclass <-> type inference interaction more smooth.
Daniel Rogozin (Mar 12 2023 at 20:14):
I think I've gotta learn what outParam
does in-depth. TBH, I was using that as a black box thinking that it's somewhat similar to fundeps in Haskell
Henrik Böving (Mar 12 2023 at 20:24):
What I explained in my first message is precisely what outParam does. THe second one just describes implications that using or not using outParam can have in applications.
Daniel Rogozin (Mar 12 2023 at 20:31):
What I explained in my first message is precisely what outParam does.
Thanks, I got it!
I meant also playing with more examples to understand deeply how to use outParam
in a more appropriate way.
Last updated: Dec 20 2023 at 11:08 UTC