-
Notifications
You must be signed in to change notification settings - Fork 1.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Test case for new typeclass derivation scheme #6531
Conversation
For reference, here's the proposal by @milessabin https://github.com/dotty-staging/dotty/commits/topic/type-class-derivation To see the code the compiler needs to generate see ADTs_2.scala, |
I noted the failures come from #6319. It worked fine on my local branch where I have not merged #6319 yet. Need to investigate further what goes wrong here /cc @OlivierBlanvillain |
Regarding c1fbed9 it's not clear to me that we want to keep |
d5f49b5
to
314567a
Compare
@odersky I've just confirmed on my branch that it's safe to make I've also seen the failures due to #6319. I'll try and boil these down to a couple of standalone reproductions. The branch itself contains a few fairly clunky workarounds for match type issues and I'd rather make the code that I really want to work, work, rather than making the workarounds work. |
|
@milessabin I went back to the type member scheme, since it allows a nicer treatment of singleton mirrors. The problem is that you can't write: class EnumValue extends Mirror.Singleton[this.type] { ... } the |
What needs to be generated ahead of time:
I believe we have found the optimum now. |
@milessabin How should we proceed on this? I can change |
@odersky yes, that would be perfect :-) |
be28902
to
62b3c6a
Compare
library/src/scala/deriving.scala
Outdated
sealed trait Mirror { | ||
|
||
/** The mirrored *-type */ | ||
type MonoType |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's the idea behind the MonoType
name? It there a PolyType
planned for later? My first intuition would be to call it something like MirroredType
or SelfType
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I let Miles answer that. The name comes from him.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See my branch. MirroredType
is used there as the type with the same kind as the type being mirrored. MonoType
is MirroredType
existentially quantified. I'll add MirroredType
later along with some differently kinded examples.
library/src/scala/deriving.scala
Outdated
/** The Mirror for a sum type */ | ||
trait Sum extends Mirror { self => | ||
|
||
type ElemTypes <: Tuple |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/** The types of the elements */
|
||
/** The ordinal number of the case class of `x`. For enums, `ordinal(x) == x.ordinal` */ | ||
def ordinal(x: MonoType): Int | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't we also need the counterpart to type CaseLabel <: String
for sums?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've argued that we do. I think @odersky agreed that it could be added later. I'm happy to add that if he still agrees.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have changed now to Label
for both sums and products.
|
||
/** For an enum T: | ||
* | ||
* def ordinal(x: MonoType) = x.enumTag |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm skeptical about having to special case any of this for enum. I would expect the generic derivation framework to simply fall apart in the case where enums a desugared to vals since there is nothing to populate Mirror.Sum.ElemTypes
...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, but there is: The singleton types of the enum values.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also see my branch ... all the Nil
-like types fall into the same category.
def fromProduct(p: scala.Product): MonoType | ||
} | ||
|
||
trait Singleton extends Product { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How is this different from an empty product? Why do we need such distinction?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
First, it's an optimization since we do not need to spell out ElemTypes and CaseLabels.
Second, it's important for printing. A case class case class C()
should print as C()
, but
a case object case object C
should print as C
. We can tell the difference since the mirror
of the case object is a Mirror.Singleton
.
.refinedWith(tpnme.ElemLabels, TypeAlias(TypeOps.nestedPairs(elemLabels))) | ||
val modul = cls.linkedClass.sourceModule | ||
assert(modul.is(Module)) | ||
ref(modul).withSpan(span).cast(mirrorType) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's preventing us from simply using actual type of modul
here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I a bit confused about these implicits. If we need them, why synthesize them as opposed to adding an implicit val mirror: ... = this
in companion objects?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because there is not always a companion object
- sealed traits might come without one
- enum cases don't have one either
tp.info match { | ||
case info: MatchAlias => | ||
mapOver(tp) | ||
// TODO: We should follow the alias in this case, but doing so |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this just about error reporting (proper cyclic reference instead of a stack overflow), or do you actually have a type correct program that makes the compiler loop here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The second: if we would follow the alias here instead of just doing a mapOver
every recursive match type would loop.
Summary of measured differences with the old scheme:
Advantages of new scheme:
|
The type class code gen size and compile time results you're reporting are what you'd expect from a scheme based on aggressive inlining, as you've done here. The erased scheme in my branch should improve significantly on both :-) |
Cool. We should run the same tests with the new scheme as well once it is ready. It's pos-special/typeclass-scaling.scala. Instructions are in the test. |
Playing with it now ... looks good :-) From my PoV the only thing missing is the |
I can give it a shot since I am still on the branch. |
@milessabin Actually, I have a question about I know that, if But what if EDIT: Looking at your examples, I conclude it should be |
Yes, that's correct. |
While experimenting with the current state of this branch I had to make the following change, --- library/src/scala/deriving.scala
+++ library/src/scala/deriving.scala
@@ -19,7 +19,7 @@ object deriving {
trait Sum extends Mirror { self =>
/** The types of the alternatives */
- type MirroredElemTypes <: Tuple
+ //type MirroredElemTypes <: Tuple
/** The ordinal number of the case class of `x`. For enums, `ordinal(x) == x.ordinal` */
def ordinal(x: MirroredMonoType): Int
@@ -29,7 +29,7 @@ object deriving {
trait Product extends Mirror {
/** The types of the product elements */
- type MirroredElemTypes <: Tuple
+ //type MirroredElemTypes <: Tuple
/** The names of the product elements */
type MirroredElemLabels <: Tuple
@@ -53,9 +53,9 @@ object deriving {
def fromProduct(p: scala.Product) = value
}
- type Of[T] = Mirror { type MirroredMonoType = T }
- type ProductOf[T] = Mirror.Product { type MirroredMonoType = T }
- type SumOf[T] = Mirror.Sum { type MirroredMonoType = T }
+ type Of[T] = Mirror { type MirroredMonoType = T ; type MirroredElemTypes <: Tuple }
+ type ProductOf[T] = Mirror.Product { type MirroredMonoType = T ; type MirroredElemTypes <: Tuple }
+ type SumOf[T] = Mirror.Sum { type MirroredMonoType = T ; type MirroredElemTypes <: Tuple }
} The issue that this addresses is that the declaration, trait Product/Sum extends Mirror {
...
type MirroredElemTypes <: Tuple
...
} commits us to You can see this on my branch in |
We got a community build failure since a companion object acquired a `Label` type member by auto-generating a `Mirror` parent. To avoid name clashes (or at least make thme very unlikely), we now systematically prefix all type fields with `Mirrored`. We probably should do something similar to the auto-generated `ordinal` and `fromProduct` methods.
Summary: - Code size increases by 13% (counting characters) to 15% (counting words) - Compile time increases by 3% (wallclock) to 4% (user) Runtime should be somewhat better for new scheme since there are fewer allocations. (Btw, the -Yshow-no-inlines option in the old measurements should be ignored; it is no linger valid and was not included in the tests)
Drop old deriving infrastructure that was based on reflect.Generic
Rebased. There's more to come, but I wanted to get this on top of the poly functions stuff now that it's merged. |
If derived instances are created in companions with the Synthetic flag set then widenImplied will widen too far, and the derived instances will have too low a priority to be selected over a freshly derived instances at the summoning site.
* Mirrors of data types of kinds other than * are now generated. The Mirror type member MirroredTypeConstructor has been renamed to MirroredType: this is the type which is used to select the "naturally" kinded Mirror (ie. the Mirror with the kind that matches the kind of the data type). * Data types can now have derives clauses for type class which are indexed by types of the same kind, for all kinds. Data types continue to support derives clauses for type classes indexed by types of lower kinds than the data type via polymorphic derived members.
I've updated my branch relative to this: dotty-staging@4e434e0. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some minor changes needed, afterwards this can go in.
Awesome :-) I'll finish this up later today ... |
I'll merge as soon as this goes green. |
type MirroredMonoType = this.type | ||
type MirroredType = this.type | ||
type MirroredElemTypes = Unit | ||
type MirroredElemLabels = Unit |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This fails when compiling from scala 2 as Unit is not a subtype of Tuple
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's the best way to fix this? We can remove the bound in Mirror
(if we do we need to add an & Tuple
at one use-site in tests/run/typeclass-derivation3.scala
)? Or we could move this file to src-3.x
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, we should move it to src-3.x
. We may also need a dummy version of it in src-2.x
the only defines classes, methods and types that we refere to in Definitions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could the dummy version be identical, but without the Tuple
bound?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is possible.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
PR with a fix here: #6609.
A simplified test case which adopts several improvements from Miles Sabin's branch.
Compared to typeclass-derivation2c.scala:
no additional classes are generated.
the previous schemes instead of simple counting, as was
the case in typeclass-derivation2c.scala.
The key insight is that we do not need a mapping from the sum type
to its alternatives except knowing what the alternative types are.
Once we have an alternative type, we can use an implicit match
to retrieve the mirror for that alternative.