-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
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
Make Id covariant #3264
Make Id covariant #3264
Conversation
Codecov Report
@@ Coverage Diff @@
## master #3264 +/- ##
=======================================
Coverage 93.08% 93.08%
=======================================
Files 376 376
Lines 7428 7428
Branches 196 196
=======================================
Hits 6914 6914
Misses 514 514
Continue to review full report at Codecov.
|
The variance of a type alias was taken to be the variance of its right-hand side, but that doesn't make sense for a parameterized type alias which is free to appear in any position: variance checking should only kick in when it is applied to something. It was possible to work around this by using a type lambda instead of a type alias, cats just had to do that: typelevel/cats#3264
The variance of a type alias was taken to be the variance of its right-hand side, but that doesn't make sense for a parameterized type alias which is free to appear in any position: variance checking should only kick in when it is applied to something. It was possible to work around this by using a type lambda instead of a type alias, cats just had to do that: typelevel/cats#3264
PR: scala/scala#8651 |
The variance of a type alias was taken to be the variance of its right-hand side, but that doesn't make sense for a parameterized type alias which is free to appear in any position: variance checking should only kick in when it is applied to something. It was possible to work around this by using a type lambda instead of a type alias, cats just had to do that: typelevel/cats#3264
Does it keep working if you type alias Welcome to Scala 2.13.1 (OpenJDK 64-Bit Server VM, Java 11.0.1).
Type in expressions for evaluation. Or try :help.
scala> trait Monad[M[_]]
defined trait Monad
scala> type Foo = { type L[+x] = x }
defined type alias Foo
scala> new Monad[Foo#L] {}
res2: Monad[[+x]x] = $anon$1@589d831e |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
@dwijnand Yes, thanks, I guess that's better even if this is just a temporary workaround. I've pushed an update with a private type alias for |
type Id[A] = A | ||
type Id[+A] = A | ||
|
||
// Workaround for a compiler bug that should be fixed soon. |
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.
now that the PR on scala is in, can we refer to it here?
scala/scala#8651
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.
@kailuowang Yes, good idea (I linked it in the PR description but the comment would be more helpful).
@LukaJCB To follow up on your question on Gitter yesterday (about whether users will also have to use the type lambda workaround), there's a type class instance for |
There's an open PR (scala/scala3#8082) against Dotty which will allow it to infer the variance of the parameters of a type alias / type lambda based on its structure: type T[X] >: A // X is invariant
type T[X] <: List[X] // X is invariant
type T[X] = List[X] // X is covariant (determined structurally)
opaque type T[X] = List[X] // X is invariant
opaque type T[+X] = List[X] // X is covariant
type T[A, B] = A => B // A is contravariant, B is covariant (determined structurally)
type T[A, +B] = A => B // A is invariant, B is covariant If that gets in, this PR shouldn't be necessary anymore. |
@smarter Is your sense that we can backport this story to Scala 2 in this case, since it's the way things happen to work anyway? |
Well if the current way of doing things works well enough with scalac in practice, it's probably not worth changing |
Another way would be to alias package object cats {
type Id[+A] = data.id.Id[A]
}
package data {
object id {
type Id[+A] = A
implicit lazy val instances = ...
}
} Although I guess there's no harm in putting the instances into type class companions, if the other stdlib types are there already and work fine |
I'm assuming you mean on Dotty? Package prefixes are most definitely part of the scope now. #2276 demonstrates how insidious this can be, and also seems quite related to this effort. |
Putting this on hold since it's no longer necessary on either Dotty or Scala 2. If someone wants to argue that it's more correct, it might be better to take that up once the Scala 2 fix is available in a release. |
Making
Id
covariant was discussed briefly in 2015:As far as I know this was never followed up on, but I just ran into an issue while trying to cross-build the Cats tests on Dotty where implicit conversions for
Id
weren't being found because it's invariant. Making it covariant would fix this problem (and also just generally seems like the right thing to do, even if the Dotty team decides that the issue is a bug and fixes it on the Dotty side).Unfortunately if we make
Id
covariant we hit a weird Scala 2 bug when defining instances for it, which minimized looks like this:And it gets even weirder:
There's some explanation by @smarter in a conversation from this afternoon on Gitter.
Update: @smarter also has a fix for the issue.
We can work around this by changing the
Id
instances toλ[A => A]
.We could also useUpdate: Scala 2 doesn't care if the variance doesn't match, but Dotty does, so I've gone with({ type L[+A] => A })#L
or the weirdλ[`+A` => A]
thing butλ[A => A]
seems to work fine.({ type L[+A] => A })#L
for now.No other changes are necessary, and we can verify that these instances are available without imports:
In the longer run, we're going to come up against another change in Dotty (this one definitely not a bug) that means that these type class instances for
Id
won't be available in implicit scope—they'll have to be imported explicitly.There are a few ways we could address this. The first would be to do nothing and expect users to import
cats._
if they need these instances. We could also create a newcats.instances.id
that they could be imported from more idiomatically (also making them available incats.instances.all
andcats.implicits
).In my view the best approach would be to provide these instances in the type class companion objects, which would ensure that they're in implicit scope on both Scala 2 and Dotty. This is something I've already done in #3043, but we could do it separately if necessary.