Skip to content

Commit 7e7d493

Browse files
committed
Update blog post
1 parent 024f828 commit 7e7d493

File tree

1 file changed

+105
-132
lines changed

1 file changed

+105
-132
lines changed

_posts/2024-08-01-given-priority-change-3.5.md

Lines changed: 105 additions & 132 deletions
Original file line numberDiff line numberDiff line change
@@ -2,94 +2,116 @@
22
layout: blog-detail
33
post-type: blog
44
by: Oliver Bračevac, EPFL
5-
title: "Changes to Givens in Scala 3.5"
5+
title: "Upcoming Changes to Givens in Scala 3.7"
66
---
77

8-
## New Prioritization of Givens
8+
## New Prioritization of Givens in Scala 3.7
99

10-
Starting with Scala 3.5, the prioritization of givens has been
11-
improved to better handle inheritance triangles, resulting in enhanced
12-
typeclass support.
10+
Scala 3.7 introduces changes to how `givens` are resolved, which can
11+
affect program behavior when multiple givens are present.
12+
13+
For example, consider a library that provides a default
14+
`given` for a component:
15+
```scala
16+
// library code
17+
class LibComponent:
18+
def msg = "library-defined"
19+
20+
// default provided by library
21+
given libComponent: LibComponent = LibComponent()
22+
23+
def printComponent(using c:LibComponent) = println(c.msg)
24+
```
25+
26+
Up until Scala 3.6, clients of the library could override
27+
`libComponent` with a user-defined one through subtyping
28+
29+
```scala
30+
// client code
31+
class UserComponent extends LibComponent:
32+
override def msg = "user-defined"
33+
34+
given userComponent: UserComponent = UserComponent()
35+
36+
@main def run = printComponent
37+
```
38+
39+
Let's run the example:
40+
41+
```scala
42+
run // Scala <= 3.6: prints "user-defined"
43+
// Scala 3.7: prints "library-defined"
44+
```
45+
46+
What happened? In Scala 3.6 and earlier, the compiler prioritizes the
47+
`give`n with the _most specific_ compatible subtype
48+
(`userComponent`). However, in Scala 3.7, it selects the value with the
49+
_most general_ subtype instead (`libComponent`).
50+
51+
52+
### Motivation: Better Handling of Inheritance Triangles & Typeclasses
53+
54+
Why change the priority to the `given` with the most general subtype?
55+
This adjustment makes working with inheritance triangles more
56+
intuitive.
57+
58+
For example, functional programmers will recognize the following
59+
inheritance triangle of common typeclasses:
1360

14-
Consider a scenario with the following inheritance triangle of type
15-
classes:
1661
```scala
1762
trait Functor[F[_]]:
1863
extension [A, B](x: F[A]) def map(f: A => B): F[B]
1964
trait Monad[F[_]] extends Functor[F] { ... }
2065
trait Traverse[F[_]] extends Functor[F] { ... }
2166
```
22-
and corresponding instances:
67+
Now, suppose we have corresponding instances of these typeclasses for `List`:
2368
```scala
24-
given a:Functor[List] = ...
25-
given b:Monad[List] = ...
26-
given c:Traverse[List] = ...
69+
given Functor[List] = ...
70+
given Monad[List] = ...
71+
given Traverse[List] = ...
2772
```
28-
Now, let’s use these in the following context:
73+
Let’s use these in the following context:
2974
```scala
3075
def fmap[F[_] : Functor, A, B](c: F[A])(f: A => B): F[B] = c.map(f)
76+
3177
fmap(List(1,2,3))(_.toString)
32-
// ^ rejected by Scala < 3.5, now accepted by Scala 3.5
78+
// ^ rejected by Scala < 3.7, now accepted by Scala 3.7
3379
```
3480

35-
Before Scala 3.5, the compiler would reject the `fmap` call due to
36-
ambiguity. Since it prioritizes the `given` instance with the _most
37-
specific_ subtype of the context bound `Functor`, both `c` and `b` are
38-
valid candidates for `Functor[List]`, but neither is more specific
39-
than the other. However, all we really need is the functionality of
40-
`a:Functor[List]`!
81+
Before Scala 3.7, the compiler would reject the `fmap` call due to
82+
ambiguity. Since it prioritized the `given` instance with the most
83+
specific subtype of the context bound `Functor`, both `Monad[List]` and
84+
`Traverse[List]` were valid candidates for `Functor[List]`, but neither
85+
was more specific than the other. However, all that’s required is the
86+
functionality of `Functor[List]`, the _most general_ instance, which Scala
87+
3.7 correctly picks.
4188

42-
In Scala 3.5, the compiler now selects the instance with the _most
43-
general_ subtype that satisfies the context bound of `fmap`. In this
44-
case, it chooses the desired `a:Functor[List]`.
89+
This change aligns the behavior of the compiler with the practical
90+
needs of developers, making the handling of common triangle inheritance
91+
patterns more predictable.
4592

46-
Inheritance triangles like this are common in practice, and the
47-
prioritization change in Scala 3.5 makes working with them more
48-
intuitive and straightforward.
93+
## Migrating to the New Prioritization
4994

5095
### Community Impact
5196

52-
Based on our evaluation using the [open community
53-
build](https://github.com/VirtusLab/community-build3), the impact of
54-
this change on existing Scala 3 projects has been minimal. However,
55-
there may still be cases where the behavior of existing programs
56-
changes due to the new prioritization of givens. Cf. below for
57-
tips to migrate to Scala 3.5.
58-
59-
60-
## Tips for Migrating to the New Prioritization
61-
62-
63-
In some cases, the new prioritization might silently select the wrong
64-
`given`. For example, consider a library that provides a default
65-
`given` for a component:
66-
```scala
67-
// library code
68-
class LibComponent:
69-
def msg = "library-defined"
70-
71-
// default provided by library
72-
given libComponent: LibComponent = LibComponent()
73-
74-
def printComponent(using c:LibComponent) = println(c.msg)
75-
```
97+
We have conducted experiments on the [open community
98+
build](https://github.com/VirtusLab/community-build3) that showed that
99+
the proposed scheme will result in a more intuitive and predictable
100+
given resolution. The negative impact on the existing projects is very
101+
small. We have tested 1500 open-source libraries, and new rules are
102+
causing problems for less than a dozen of them.
76103

77-
Clients of the library might have relied on the “most specific”
78-
prioritization to override the default given with a user-defined one:
79-
```scala
80-
// client code
81-
class UserComponent extends LibComponent:
82-
override def msg = "user-defined"
104+
### Roadmap
83105

84-
given userComponent: UserComponent = UserComponent()
106+
The new `given` resolution scheme, which will be the default in Scala
107+
3.7, can already be explored in Scala 3.5. This early access allows
108+
the community ample time to test and adapt to the upcoming changes.
85109

86-
@main def run = printComponent
87-
// Scala < 3.5: prints "user-defined"
88-
// Scala 3.5: prints "library-defined"
89-
```
110+
**Scala 3.5**
90111

91-
Scala 3.5 will automatically issue
92-
warnings when the choice of `given` has changed:
112+
Starting with Scala 3.5, you can compile with `-source 3.6` to receive
113+
warnings if the new `given` resolution scheme would affect your
114+
code. This is how the warning might look:
93115

94116
```scala
95117
-- Warning: client.scala:11:30 ------------------------------------------
@@ -101,21 +123,21 @@ warnings when the choice of `given` has changed:
101123
| (libComponent : LibComponent)
102124
| has changed.
103125
| Previous choice : the first alternative
104-
| New choice from Scala 3.6: the second alternative
126+
| New choice from Scala 3.7: the second alternative
105127
```
106128

129+
Additionally, you can compile with `-source 3.7` or `-source future`
130+
to fully enable the new prioritization and start experiencing its
131+
effects.
132+
133+
**Scala 3.6**
107134

108-
### Useful Compiler Options
135+
In Scala 3.6, these warnings will be on by default.
109136

110-
In future releases (Scala 3.6+), automatic warnings related to changes
111-
in the selection of givens, as described above, will no longer be
112-
issued by default. However, these warnings can be reactivated using
113-
the `-source:3.5` option with `scalac`.
137+
**Scala 3.7**
114138

115-
Additionally, combining Scala 3.5 with the `-source:3.6` option can be
116-
useful to verify that implicit search results will not be ambiguous in
117-
future versions or to test your application at runtime with the new
118-
rules in effect.
139+
Scala 3.7 will finalize the transition, making the new given
140+
prioritization the standard behavior.
119141

120142
#### Suppressing Warnings
121143

@@ -146,17 +168,22 @@ val x = summon[A]
146168

147169
For more details, you can consult the guide on [configuring and suppressing warnings]({{ site.baseurl }}/2021/01/12/configuring-and-suppressing-warnings.html).
148170

149-
### Resorting to Explicit Parameters
171+
### Workarounds
150172

151-
If the pre-3.5 behavior is preferred, you can explicitly pass the
173+
Here are some practical strategies to help you smoothly adapt to the
174+
new given resolution scheme:
175+
176+
#### Resorting to Explicit Parameters
177+
178+
If the pre-3.7 behavior is preferred, you can explicitly pass the
152179
desired given:
153180
```scala
154181
@main def run = printComponent(using userComponent)
155182
```
156183

157184
To determine the correct explicit parameter (which could involve a
158-
complex expression), it can be helpful to compile with Scala 3.4 (or
159-
earlier) using the `-Xprint:typer` flag:
185+
complex expression), it can be helpful to compile with an earlier
186+
Scala version using the `-Xprint:typer` flag:
160187
```scala
161188
scalac client.scala -Xprint:typer
162189
```
@@ -167,10 +194,10 @@ This will output all parameters explicitly:
167194
...
168195
```
169196

170-
### Explicit Prioritization by Owner
197+
#### Explicit Prioritization by Owner
171198

172199
One effective way to ensure that the most specific given instance is
173-
selected -— particularly useful when migrating libraries to Scala 3.5 -—
200+
selected -— particularly useful when migrating libraries to Scala 3.7 -—
174201
is to leverage the inheritance rules as outlined in point 8 of [the
175202
language
176203
reference](https://docs.scala-lang.org/scala3/reference/changed-features/implicit-resolution.html):
@@ -196,64 +223,10 @@ hierarchies of classes that provide `given` instances. By importing the
196223
`given` instances from the object with the highest priority, you can
197224
control which instance is selected by the compiler.
198225

199-
#### Upcoming Bugfixes
200-
201-
Due to the release cut-off, Scala 3.5.0 may still report a
202-
warning that the above pattern will be ambiguous in Scala
203-
3.6. However, this warning is incorrect. The issue has already been
204-
resolved, and an upcoming bugfix release will improve the
205-
accuracy of warnings.
206-
207-
208226
### Outlook
209227

210228
We are considering adding `-rewrite` rules that automatically insert
211229
explicit parameters when a change in choice is detected.
212230

213231

214-
## Towards Context Bounds as Givens
215-
216-
We are gradually phasing out remaining uses of Scala 2 style
217-
`implicit`s in favor of the `given`/`using` syntax. Scala 3.5 marks
218-
the first step in transitioning context bounds on type parameters to
219-
givens, with this transition expected to be completed in the upcoming
220-
Scala 3.6 release.
221-
222-
Currently, context bounds on type parameters still desugar into
223-
`implicit` parameters:
224-
225-
```scala
226-
def f[Element : Eq : Ordering] = summon[Eq[Element]].toOrdering
227-
// expands to:
228-
def f[Element >: Nothing <: Any](implicit evidence$1: Eq[Element],
229-
implicit evidence$2: Order[Element]): Ordering[Element] =
230-
evidence$2.toOrdering
231-
```
232-
233-
Prior to Scala 3.5, it was possible to pass `implicit` arguments
234-
explicitly for context bounds as if they were regular arguments. In
235-
Scala 3.5, however, these parameters must be qualified with `using`:
236-
237-
```scala
238-
val eq: Eq[Int] = ???
239-
val ord: Order[Int] = ???
240-
f(eq, ord) // ok in Scala < 3.5, error in 3.5
241-
f(using eq, ord) // ok in Scala 3.5
242-
```
243232

244-
At this stage, the change does not affect the expansion of functions
245-
like `f` above, which still rely on `implicit` parameters. However,
246-
this is a crucial step towards facilitating the eventual transition to
247-
`given`s for context bounds in Scala 3.6.
248-
249-
To assist with the migration to explicit `using` clauses, Scala 3.5
250-
provides an error message and offers automatic rewrites:
251-
252-
```scala
253-
-- Error: bounds.scala:10:2 ----------------------------------------------
254-
10 | f(eq, ord) // error
255-
| ^
256-
|Context bounds will map to context parameters.
257-
|A `using` clause is needed to pass explicit arguments to them.
258-
|This code can be rewritten automatically under -rewrite -source 3.4-migration.
259-
```

0 commit comments

Comments
 (0)