-
Notifications
You must be signed in to change notification settings - Fork 325
Blog post about given prioritization changes #1675
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
Merged
Merged
Changes from 2 commits
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
6b86452
Blog post about given prioritization changes
bracevac 66dc2a7
Tweak
bracevac 67fc9a0
Update blog post
bracevac 0319c0b
Discuss explicit prioritization by owner
bracevac 41a85c9
Remark on possible spurious warnings in explicit prioritization
bracevac 024f828
Discuss how to suppress the warning
bracevac 7e7d493
Update blog post
bracevac 2f017ac
Rename the file
bracevac f570f20
Typos
bracevac 8bf4e31
Warning about ... warnings
bracevac 2adf42d
Change structure
bracevac 0610d5e
Remove an orphan
bracevac fd42c36
Deduplicate accidental duplication
bracevac File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
--- | ||
layout: blog-detail | ||
post-type: blog | ||
by: Oliver Bračevac, EPFL | ||
title: "Changes to Givens Prioritization in Scala 3.5" | ||
--- | ||
|
||
## Motivation | ||
|
||
Starting with Scala 3.5, the prioritization of givens has been | ||
improved to better handle inheritance triangles, resulting in enhanced | ||
typeclass support. | ||
|
||
Consider a scenario with the following inheritance triangle of type classes: | ||
```scala | ||
trait Functor[F[_]]: | ||
extension [A, B](x: F[A]) def map(f: A => B): F[B] | ||
trait Monad[F[_]] extends Functor[F] { ... } | ||
trait Traverse[F[_]] extends Functor[F] { ... } | ||
``` | ||
and corresponding instances: | ||
```scala | ||
given a:Functor[List] = ... | ||
given b:Monad[List] = ... | ||
given c:Traverse[List] = ... | ||
``` | ||
Now, let’s use these in the following context: | ||
```scala | ||
def fmap[F[_] : Functor, A, B](c: F[A])(f: A => B): F[B] = c.map(f) | ||
fmap(List(1,2,3))(_.toString) | ||
// ^ rejected by Scala < 3.5, now accepted by Scala 3.5 | ||
``` | ||
|
||
Before Scala 3.5, the compiler would reject the `fmap` call due to | ||
ambiguity. Since it prioritizes the `given` instance with the _most | ||
specific_ subtype of the context bound `Functor`, both `c` and `b` are | ||
valid candidates for `Functor[List]`, but neither is more specific | ||
than the other. However, all we really need is the functionality of | ||
`a:Functor[List]`! | ||
|
||
In Scala 3.5, the compiler now selects the instance with the _most | ||
general_ subtype that satisfies the context bound of `fmap`. In this | ||
case, it chooses `a:Functor[List]`. | ||
|
||
Inheritance triangles like this are common in practice, and the | ||
prioritization change in Scala 3.5 makes working with them more | ||
intuitive and straightforward. | ||
|
||
## Tips for Migrating to 3.5 | ||
|
||
Based on our evaluation using the [open community | ||
build](https://github.com/VirtusLab/community-build3), the impact of | ||
this change on existing Scala 3 projects has been minimal. However, | ||
there may still be cases where the behavior of existing programs | ||
changes due to the new prioritization of givens. | ||
|
||
In some cases, the new prioritization might silently select the wrong | ||
`given`. For example, consider a library that provides a default | ||
`given` for a component: | ||
```scala | ||
// library code | ||
class LibComponent: | ||
def msg = "library-defined" | ||
|
||
// default provided by library | ||
given libComponent: LibComponent = LibComponent() | ||
|
||
def printComponent(using c:LibComponent) = println(c.msg) | ||
``` | ||
|
||
Clients of the library might have relied on the “most specific” | ||
prioritization to override the default given with a user-defined one: | ||
```scala | ||
// client code | ||
class UserComponent extends LibComponent: | ||
override def msg = "user-defined" | ||
|
||
given userComponent: UserComponent = UserComponent() | ||
|
||
@main def run = printComponent | ||
// Scala < 3.5: prints "user-defined" | ||
// Scala 3.5: prints "library-defined" | ||
``` | ||
|
||
To detect such "silent" changes, we recommend compiling under Scala | ||
3.5 with the `-source:3.6-migration` flag: | ||
bracevac marked this conversation as resolved.
Show resolved
Hide resolved
|
||
```bash | ||
scalac client.scala -source:3.6-migration | ||
``` | ||
This will issue warnings when the choice of `given` has changed: | ||
```scala | ||
-- Warning: client.scala:11:30 ------------------------------------------ | ||
11 |@main def run = printComponent | ||
| ^ | ||
| Given search preference for LibComponent between alternatives | ||
| (userComponent : UserComponent) | ||
| and | ||
| (libComponent : LibComponent) | ||
| has changed. | ||
| Previous choice : the first alternative | ||
| New choice from Scala 3.6: the second alternative | ||
``` | ||
|
||
### Explicit Parameters | ||
|
||
If the pre-3.5 behavior is preferred, you can explicitly pass the | ||
desired given: | ||
```scala | ||
@main def run = printComponent(using userComponent) | ||
``` | ||
|
||
To determine the correct explicit parameter (which could involve a | ||
complex expression), it can be helpful to compile with Scala 3.4 (or | ||
earlier) using the `-Xprint:typer` flag: | ||
```scala | ||
scalac client.scala -Xprint:typer | ||
``` | ||
This will output all parameters explicitly: | ||
```scala | ||
... | ||
@main def run: Unit = printComponent(userComponent) | ||
... | ||
``` | ||
|
||
### Outlook | ||
|
||
We are considering adding `-rewrite` rules that automatically insert | ||
explicit parameters when a change in choice is detected. |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.