-
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
Issues with given prioritization change #22913
Comments
@odersky I wanted to discuss it during the previous core meeting since it was raised by a couple of open source contributors as a possible issue. |
I'll let Arman speak to it since he probably remembers more of the context than I do, but he and I discussed this at length back when the SIP was going through its paces. Our conclusion at the time was that the new prioritization would have no effect on Cats Effect and probably wouldn't affect Cats either, but obviously this stuff tends to be subtle and surprising. There's basically no way to be sure outside of trying to compile a ton of downstream code. The one place where I could see surprising user-facing things popping up is we tend to do some stuff which looks like this: def apply[F[_], E](implicit F: GenConcurrent[F, E]): F.type = F
def apply[F[_]](implicit F: GenConcurrent[F, ?], d: DummyImplicit): F.type = F These are the summoners for the The risk here is real though. Manipulating given prioritization in general requires tucking things into varying levels of the inheritance hierarchy, and so if we have a situation where compiling against 3.7 results in libraries outright breaking, it's possible (though not guaranteed) that we would have no way to fix this in a way which is binary compatible with previous library releases, to say nothing of source compatibility with previous Scala releases. |
you can detect the tasty version of the symbol and change semantic (sounds evil) |
The change in question didn't actually go through SIP process I think. I think it was deemed to be minor change, but in reality it might not be. |
Ah my bad. Like I said, hazy memory. I was thinking of the PR. |
No you can't. Candidate implicits can come from two sources, with both old and new semantics, but you have to pick one. |
We did in fact execute a spike into this problem. The motivation for this research was that: a) there were some rather uncomfortable warnings present in the OCB log when the original blast radius analysis was done for this change. Initially the assumption was that these were all fine and the number of ambiguous clashes was very low in OCB so we assumed it's going to be fine. b) @MateuszKubuszok gave us a hint that this change does break Chimney as it depends on a significance of typeclass hierarchy - namely for //> using scala 3.7.0-RC1
trait TypeClass[A] extends TypeClass.AutoDerived[A]
object TypeClass {
trait AutoDerived[A]
object AutoDerived {
given [A]: AutoDerived[A] = new TypeClass[A] {}
}
}
class Foo
object Foo {
given TypeClass[Foo] = new TypeClass[Foo] {}
}
def use[A: TypeClass.AutoDerived]: Unit = println(summon[TypeClass.AutoDerived[A]])
@main def main = use[Foo]
//-- [E172] Type Error: ----------------------------------------------------------
//16 |@main def main = use[Foo]
// | ^
// |Ambiguous given instances: both given instance given_AutoDerived_A in object AutoDerived and given instance //given_TypeClass_Foo in object Foo match type TypeClass.AutoDerived[Foo] of a context parameter of method use
//1 error found There is no way for this to be resolved under new rules and Chimney will have to deal with this by having two parallel major releases, on for scala 2.13 & 3.x where x < 7 and for 3.7+. To make Chimney possible for Scala 3.7+ a new macro combinator was introduced by @jchyb, This realisation made us wonder if the same will happen to cats / cats-effect and typelevel ecosystem and given the potential blast radius of this possibility, we spent some time investigating. The result of our spike is that we haven't been able to find conclusive evidence of breakage of significance similar to the one in Chimney. We have explored three approaches:
Disclaimer: these forks were in no way an attempt to port cats/CE to S3 givens in a sane and comprehensive way.
{
"change": {
"target": "cats.kernel.Eq[A]",
"first": "cats.kernel.Order[A]",
"second": "cats.kernel.Eq[A]",
"currentChoice": "FirstAlternative",
"newChoice": "SecondAlternative"
},
"count": 1
} The hierarchy is |
@lbialy That's a wonderful bit of investigation thank you. I'll try to look at your forks at some point this week. Your explanation did make me realize that we might have some problems related to this which wouldn't show up in functional testing. In particular, there are a bunch of places both in Cats and Cats Effect where a more specific instance of a typeclass overrides the definition of some concrete method with a more efficient implementation, basically leveraging the stricter guarantees it has available to it by being lower in the hierarchy. We actually even surface this information (internally) at runtime and use it to manually specialize certain things. In particular, look at Now, for concrete types like So in other words… I do think there's probably some danger here. I really would be surprised if anything functionally breaks at compile- or runtime, but I could definitely see cases where performance is silently regressed by significant amounts. |
It might or it might not be relevant but... When I was checking whether there's a way to work around the issue in Chimney without a new major version, I found out that while: foo.transformInto[Bar] // summons Transformer.AutoDerived[Foo, Bar] triggers an issue on 3.6, and and ambiguity error on 3.7 foo.into[Bar].transform does not. The later triggers a macro that does look for implicits but it if does not find one, it generates an inlined transformation. I checked that it did find the implicit, so it used the old semantics. One
so there's a chance that some issues would come from the type class derivation, in libraries that use macros, once that "bug" is fixed. Since the type class derivation with Mirrors usually relies on |
oh, I think the fact that |
@djspiewak In this case the best way to verify this (given my limited knowledge) would be to create a catalog of all C/CE typeclasses along with their parents and verify which instances get summoned for which scala version (and I'm making a generous assumption here that you can't get different instances depending on the import you use - cats.syntax.stdSomething vs cats.implicits.* for example). |
@lbialy Here's some quick resources:
Might be easier to generate something more bespoke by traversing the inheritance hierarchies. |
Why do you need a special primitive for that? Have you tried using opaque type OrElse[A, B] = A | B
inline given orElse[A, B]: OrElse[A, B] = summonFrom:
case instance: A => OrElse(instance)
case instance: B => OrElse(instance)
How did
But then this would mean it was not really tested as intended.
This is concerning, I would assume a green OCB would be a prerequisite for this kind of big change. The point about optimization in subclasses is a really good one. Specifically for generic types and monad transformers, to provide an instance you need to require an instances for your type parameters. E.g. |
OCB was mostly green for this change! It wasn't for my forks where I refactored cats/cats-effect to define implicits as given/using. The problem is a bit deeper. Cats, cats-mtl and cats-effect define their implicits as, well, implicits and not givens. Then, to make things worse, a LOT of existing code uses Regarding the special primitive - I believe that's due to a) existing code that can't be broken that b) was written during 2.11, then refactored during 2.12 and 2.13 and then updated to work with Scala 3. No |
Wouldn't it be quite confusing that |
I think I may need to add some context, why Chimney needed such scheme.
In other words, having the macro avoid implicit search that resolves to itself, enables better error messages (even for nested fields), better runtime performance and better compilation times. It does not affect correctness, since it's only about whether there is several intermediate type class instances instead of one, having their code inlined, but it improves only UX which is probably why nobody cared about doing such fancy tricks in their libraries. Anyway, this pattern was a way to achieve just that: recursive derivation that is not broken by enabling automatic derivation: having extension methods summon either user-provided instances or falling back to derived ones, and having macros always treating user-provided instances as "overrides" of the default behavior, and using plain-old recursion otherwise. trait TypeClass[A] extends TypeClass.AutoDerived[A]
object TypeClass {
trait AutoDerived[A]
object AutoDerived {
// Macro looks only for TypeClass, if there is none, it generates the transformation
// while handling the transformation recursively
inline given [A]: AutoDerived[A] = ${ ... }
}
}
def extension[From](value: A)
// If user provided TypeClass[A], it would be preferred
// it not, it will be derived as TypeClass.AutoDerived[A]
def useTypeClass(using t: TypeClass.AutoDerived[A]): A = ... This was designed in such a way to have the requirement fulfilled AND keep the same API on: 2.12, 2.13, 3. If somebody does I did researched
so we would have to add some other hacky workarounds for that as well... Then, there is the risk that yet another SIP would pull the rug from under us again, and then we would need another rewrite, and another, and another... Luckily for us, But still, it only works for 3.7.0+, while the previous version worked on 2.12, 2.13 and everything before 3.7.0, so at least for us the prophecy about needing to support 2 versions: pre- and post-3.7.0 has fulfilled. |
I have asked @EugeneFlesselle to study this and comment. |
This comment is more of a summary of the thread than proposing solutions. The original question was how to address situations for libraries (with a particular focus on the Chimney and Cats libraries) where:
The two main cases discussed where selecting the most general instance was not the desirable outcome are:
Another concern having been brought up is that
The high-level conclusions from the two libraries were that:
The decision is now whether there is anything to do from the point of view of the language or if there are recommendations we should make for library authors/users. |
Thanks for the summary! One conclusion for me is that we might want to move forward with #22580, so that we can give early warnings to library developers about possible breakages when moving from implicits to givens. |
This is very surprising. AFAIK, there is no logic applying a different resolution scheme from the inliner and/or macros. I verified with @hamzaremmal that we do select the most general instance for I'm not familiar with the definitions used in the two code snippets from #22913 (comment), @MateuszKubuszok could there be another reason they yielded different results? |
@MateuszKubuszok you had a minimizer available, didn't you? |
@lbialy, @EugeneFlesselle I have this Scastie snippet - it shows how:
AFAIR The warning appeared in 3.5 together with |
With 3.7.0 the new given prioritization comes into effect, which might cause libraries like chimney or cats-effect to not work properly even if compiled with an earlier version.
So in essence:
Do we have a way to fix it for users? We could potentially use the source flag in the user code to revert to older priority, but that makes them unable to use any new features and stuck with 3.6.0.
We could also add a separate flag for that case, but that make everyone need to use that flag until cats reimplement the given resolution, which I am not even sure is doable.
Another, bad idea, is to cross compile pre and after 3.7.0, but that is not something we wanted to ever do.
Do we have any sensible solution to that problem? Cats-effect is one of the wider used libraries, so it would be great to be able to fix the issue even before the new priority comes into effect.
Since I don't know much about details it would be great to hear from someone who knows more details
CC
@armanbilge @lbialy @WojciechMazur
I think Wojciech and Łukasz were doing some tests, but not sure if they managed to find a conclusion.
I have limited knowledge here, so it's possible it's not an issue.
The text was updated successfully, but these errors were encountered: