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
Covariant HKT implicits fail to resolve #11427
Comments
I think the problem boils down to:
Note that trait MonoIO[F[_]]
trait BifunctorIO[F[+_, _]]
case class IO[+A]()
object MonoIO {
implicit val monoInstance: MonoIO[IO] = new MonoIO[IO]{}
}
trait AnyIO[+F[_]]
object AnyIO {
implicit def fromMono[F[_]: MonoIO]: AnyIO[F] = new AnyIO[F]{}
implicit def fromBIO[F[+_, _]: BifunctorIO]: AnyIO[({ type K[A] = F[Nothing, A] })#K] = new AnyIO[({ type K[A] = F[Nothing, A] })#K]{}
} |
@joroKr21
Meaning all supertypes of all type parameters, projection/selection prefixes, intersections and of the type itself are ALL searched. And also this example works, showing that the companion of the supertype must have been searched: object example extends App {
class C[+A]
trait TBase
object TBase {
implicit def CTBase[T <: TBase]: C[T] = new C[T]{}
}
class T extends TBase
implicitly[C[T]]
def x[X <: TBase] = implicitly[C[X]]
implicitly[C[X forSome { type X <: TBase}]]
} Point 2 is the bug in question indeed, the |
@Kaishh Ah yes, you are right, I completely missed that part of implicits for some reason. So I guess the problem is that while in your second example the fact that Edit: Your original example doesn't work even if the type is not higher-kinded. |
@joroKr21 |
Don't know, but what @joroKr21 is saying seems plausible, Dotty does go through upper bounds of type variables to decide what companions to look for to find implicits, I think Scala 2 does the same, but maybe it doesn't do it recursively or something ? |
(Also I don't recommend using Scastie to infer Dotty's behavior because it's stuck on an old version of Dotty because of scalacenter/scastie#281) |
Works on dotty-0.10.0-RC1 too.
Ok, would that be hard to fix? And if, hypothetically, there was a fix, would it be accepted into 2.12(.9, .10) series or would that be considered a breaking change? |
No idea about how hard it'd be to fix :). Probably unlikely to get into 2.12 since it could break source compatibility in non-obvious ways. |
This works on 2.12 with |
Unfortunately, that was a false positive. The example DOES NOT work under any of |
We can't tackle this in 2.13.0 anymore, but before assigning this to 2.13.1, I'd like to first understand whether this is actually a regression. |
This is the method that computes the implicit scope of a type: There is no special handling of type variables. All abstract types end up here: As you can see only the upper bound is considered and since Scala 2.13 also the prefix and type arguments, but definitely no type constraints are considered. Also the doc says this method is performance critical. |
Yeah agreed if the spec interpretation that type variable constraints should count towards the implicit scope is correct, then this should be fixed. At this point the dilemma for me is would a change in this area be accepted in 2.13 and if not is it feasible to fix this in Scala 2 or just waiting for Scala 3 would be the better choice? |
@joroKr21
|
I think it would help to simplify the example. Here's an equivalent version that doesn't use type constructors, but fails in the same way. Most likely because class MonoIO[F] // it works if you make this covariant
class IO
object IO { implicit val monoInstance: MonoIO[IO] = new MonoIO[IO] }
class AnyIO[+F] // also works if you drop covariance here (and don't add it above)
object AnyIO { implicit def fromMono[F](implicit mono: MonoIO[F]): AnyIO[F] = new AnyIO[F] }
object Test {
// if we provide this explicitly, it works even for any mix of variance:
// implicit val ev = AnyIO.fromMono[IO]
implicitly[AnyIO[IO]] //(AnyIO.fromMono) even with this hint, type inference still fails
} This all points to a pretty fundamental limitation in Scala 2 type inference, which we likely can't fix in Scala 2. There's a lot of tickets on |
I don't think we have a |
I suggest compiling the variants in my example with the following patch, and you can observe what happens: --- i/src/compiler/scala/tools/nsc/typechecker/Infer.scala
+++ w/src/compiler/scala/tools/nsc/typechecker/Infer.scala
@@ -523,6 +523,7 @@ trait Infer extends Checkable {
}
}
+ println(s"adjustTypeArgs: $tparams --> ${okParams.zip(okArgs)} ++ $undetParams / $tvars / $targs under $restpe")
AdjustedTypeArgs(okParams.toList, okArgs.toList, undetParams.toList, allArgs.toList)
}
|
Spoiler alert:
versus:
|
But with this patch: --- a/src/compiler/scala/tools/nsc/typechecker/Implicits.scala
+++ b/src/compiler/scala/tools/nsc/typechecker/Implicits.scala
@@ -1444,7 +1444,10 @@ trait Implicits {
case None =>
val start = if (StatisticsStatics.areSomeColdStatsEnabled) statistics.startTimer(subtypeETNanos) else null
// val implicitInfoss = companionImplicits(pt)
- val implicitInfoss1 = companionImplicitMap(pt).valuesIterator.toList
+ val infoMap = companionImplicitMap(pt)
+ val typeArgs = pt.typeArgs.map(targ => targ.typeSymbol -> targ.typeSymbol.info)
+ println(s"$pt ($typeArgs) -> $infoMap")
+ val implicitInfoss1 = infoMap.valuesIterator.toList
// val is1 = implicitInfoss.flatten.toSet
// val is2 = implicitInfoss1.flatten.toSet
// for (i <- is1) I get:
But I was wrong about one thing, here And anyway if it's not about implicit scope why would bringing the implicit instance into scope matter? |
because it's a transitive implicit search, I think -- method calls are handled differently than just using the local val that's already fully typed |
Adding an upper bound also makes it compile: class MonoIO[F]
class IO
object IO { implicit val monoInstance: MonoIO[IO] = new MonoIO[IO] }
class AnyIO[+F] // upper bound in the definition is considered for implicit scope
object AnyIO { implicit def fromMono[F <: IO](implicit mono: MonoIO[F]): AnyIO[F] = new AnyIO[F] }
object Test {
implicitly[AnyIO[IO]] //(AnyIO.fromMono) even with this hint, type inference still fails
} |
So I have a question - instead of keeping the original |
@adriaanm see what I meant here: scala/scala#8582 |
Given this setup:
Constructing SomeAlg fails even when the types are fully annotated:
Explicitly providing
AnyIO
instance works though:SomeAlg.make[IO](AnyIO.fromMono[IO])
Removing covariance from
AnyIO
allowsSomeAlg.make[IO]()
to work, however, a non-type annotated version will still fail to resolve:This is only fixed if covariance is removed from
SomeAlg
too.The reason for wanting covariance on these classes is to represent errorless effects, e.g. Clock, Random, Logging:
^ The covariance on the example clock algebra is extremely convenient as it allows it to transition seamlessly between bifunctor (arbitrary error type) and monofunctor (fixed error type) contexts. However, with the bug above, these algebras are extremely limited in how they can participate in implicit resolution.
The text was updated successfully, but these errors were encountered: