Skip to content

Commit 6b86452

Browse files
committed
Blog post about given prioritization changes
1 parent 2e7741e commit 6b86452

File tree

1 file changed

+128
-0
lines changed

1 file changed

+128
-0
lines changed
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
---
2+
layout: blog-detail
3+
post-type: blog
4+
by: Oliver Bračevac, EPFL
5+
title: "Changes to Givens Prioritization in Scala 3.5"
6+
---
7+
8+
## Motivation
9+
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.
13+
14+
Consider a scenario with the following inheritance triangle of type classes:
15+
```scala
16+
trait Functor[F[_]]:
17+
extension [A, B](x: F[A]) def map(f: A => B): F[B]
18+
trait Monad[F[_]] extends Functor[F] { ... }
19+
trait Traverse[F[_]] extends Functor[F] { ... }
20+
```
21+
and corresponding instances:
22+
```scala
23+
given a:Functor[List] = ...
24+
given b:Monad[List] = ...
25+
given c:Traverse[List] = ...
26+
```
27+
Now, let’s use these in the following context:
28+
```scala
29+
def fmap[F[_] : Functor, A, B](c: F[A])(f: A => B): F[B] = c.map(f)
30+
fmap(List(1,2,3))(_.toString)
31+
// ^ rejected by Scala < 3.5, now accepted by Scala 3.5
32+
```
33+
34+
Before Scala 3.5, the compiler would reject the `fmap` call due to
35+
ambiguity. Since it prioritizes the `given` instance with the _most
36+
specific_ subtype of the context bound `Functor`, both `c` and `b` are
37+
valid candidates for `Functor[List]`, but neither is more specific
38+
than the other. However, all we really need is the functionality of
39+
`a:Functor[List]`!
40+
41+
In Scala 3.5, the compiler now selects the instance with the _most
42+
general_ subtype that satisfies the context bound of `fmap`. In this
43+
case, it chooses `a:Functor[List]`.
44+
45+
Inheritance triangles like this are common in practice, and the
46+
prioritization change in Scala 3.5 makes working with them more
47+
intuitive and straightforward.
48+
49+
## Tips for Migrating to 3.5
50+
51+
Based on our evaluation using the [open community
52+
build](https://github.com/VirtusLab/community-build3), the impact of
53+
this change on existing Scala 3 projects has been minimal. However,
54+
there may still be cases where the behavior of existing programs
55+
changes due to the new prioritization of givens.
56+
57+
In some cases, the new prioritization might silently select the wrong
58+
`given`. For example, consider a library that provides a default
59+
`given` for a component:
60+
```scala
61+
// library code
62+
class LibComponent:
63+
def msg = "library-defined"
64+
65+
// default provided by library
66+
given libComponent: LibComponent = LibComponent()
67+
68+
def printComponent(using c:LibComponent) = println(c.msg)
69+
```
70+
71+
Clients of the library might have relied on the “most specific”
72+
prioritization to override the default given with a user-defined one:
73+
```scala
74+
// client code
75+
class UserComponent extends LibComponent:
76+
override def msg = "user-defined"
77+
78+
given userComponent: UserComponent = UserComponent()
79+
80+
@main def run = printComponent
81+
// Scala < 3.5: prints "user-defined"
82+
// Scala 3.5: prints "library-defined"
83+
```
84+
85+
To detect such "silent" changes, we recommend compiling under Scala
86+
3.5 with the `-source:3.6-migration` flag:
87+
```bash
88+
scalac client.scala -source:3.6-migration
89+
```
90+
This will issue warnings when the choice of `given` has changed:
91+
```scala
92+
-- Warning: client.scala:11:30 ------------------------------------------
93+
11 |@main def run = printComponent
94+
| ^
95+
| Given search preference for LibComponent between alternatives
96+
| (userComponent : UserComponent)
97+
| and
98+
| (libComponent : LibComponent)
99+
| has changed.
100+
| Previous choice : the first alternative
101+
| New choice from Scala 3.6: the second alternative
102+
```
103+
104+
### Explicit Parameters
105+
106+
If the pre-3.5 behavior is preferred, you can explicitly pass the
107+
desired given:
108+
```scala
109+
@main def run = printComponent(using libComponent)
110+
```
111+
112+
To determine the correct explicit parameter (which could involve a
113+
complex expression), it can be helpful to compile with Scala 3.4 (or
114+
earlier) using the `-Xprint:typer` flag:
115+
```scala
116+
scalac client.scala -Xprint:typer
117+
```
118+
This will output all parameters explicitly:
119+
```scala
120+
...
121+
@main def run: Unit = printComponent(libComponent)
122+
...
123+
```
124+
125+
### Outlook
126+
127+
We are considering adding `-rewrite` rules that automatically insert
128+
explicit parameters when a change in choice is detected.

0 commit comments

Comments
 (0)