Skip to content

Commit 2adf42d

Browse files
committed
Change structure
Open with the motivation first, then with the negative part.
1 parent 8bf4e31 commit 2adf42d

File tree

1 file changed

+109
-18
lines changed

1 file changed

+109
-18
lines changed

_posts/2024-08-19-given-priority-change-3.7.md

Lines changed: 109 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,61 @@ title: "Upcoming Changes to Givens in Scala 3.7"
77

88
## New Prioritization of Givens in Scala 3.7
99

10-
Scala 3.7 will introduce changes to how `given`s are resolved, which can
11-
affect program behavior when multiple `given`s are present.
10+
Scala 3.7 will introduce changes to how `given`s are resolved, which
11+
may affect program behavior when multiple `given`s are present. The
12+
aim of this change is to make `given` resolution more predictable, but
13+
it could lead to problems during migration to Scala 3.7 or later
14+
versions. In this article, we’ll explore the motivation behind these
15+
changes, potential issues, and provide migration guides to help
16+
developers prepare for the transition.
1217

13-
For example, consider a library that provides a default
18+
### Motivation: Better Handling of Inheritance Triangles & Typeclasses
19+
20+
The motivation for changing the prioritization of `given`s stems from
21+
the need to make interactions within inheritance hierarchies,
22+
particularly inheritance triangles, more intuitive. This adjustment
23+
addresses a common issue where the compiler struggles with ambiguity
24+
in complex typeclass hierarchies.
25+
26+
For example, functional programmers will recognize the following
27+
inheritance triangle of common typeclasses:
28+
29+
```scala
30+
trait Functor[F[_]]:
31+
extension [A, B](x: F[A]) def map(f: A => B): F[B]
32+
trait Monad[F[_]] extends Functor[F] { ... }
33+
trait Traverse[F[_]] extends Functor[F] { ... }
34+
```
35+
Now, suppose we have corresponding instances of these typeclasses for `List`:
36+
```scala
37+
given Functor[List] = ...
38+
given Monad[List] = ...
39+
given Traverse[List] = ...
40+
```
41+
Let’s use these in the following context:
42+
```scala
43+
def fmap[F[_] : Functor, A, B](c: F[A])(f: A => B): F[B] = c.map(f)
44+
45+
fmap(List(1,2,3))(_.toString)
46+
// ^ rejected by Scala < 3.7, now accepted by Scala 3.7
47+
```
48+
49+
Before Scala 3.7, the compiler would reject the `fmap` call due to
50+
ambiguity. Since it prioritized the `given` instance with the _most
51+
specific_ subtype of the context bound `Functor`, both `Monad[List]`
52+
and `Traverse[List]` were valid candidates for `Functor[List]`, but
53+
neither was more specific than the other. However, all that’s required
54+
is the functionality of `Functor[List]`, the instance with the _most
55+
general_ subtype, which Scala 3.7 correctly picks.
56+
57+
This change aligns the behavior of the compiler with the practical
58+
needs of developers, making the handling of common triangle
59+
inheritance patterns more predictable.
60+
61+
### Source Incompatibility of the New Givens Prioritization
62+
63+
Unfortunately, this change might affect source compatibility of some Scala codebases.
64+
Let's consider a library that provides a default
1465
`given` for a component:
1566
```scala
1667
// library code
@@ -48,47 +99,87 @@ What happened? In Scala 3.6 and earlier, the compiler prioritizes the
4899
(`userComponent`). However, in Scala 3.7, it selects the value with the
49100
_most general_ subtype instead (`libComponent`).
50101

102+
Here's the revised version with improved transitions and adjustments for the reordering:
103+
104+
## New Prioritization of Givens in Scala 3.7
105+
106+
Scala 3.7 will introduce changes to how `given`s are resolved, which may affect program behavior when multiple `given`s are present. The aim of this change is to make `given` resolution more predictable, but it could lead to challenges during migration to Scala 3.7 or later versions. In this article, we’ll explore the motivation behind these changes, potential issues, and provide migration guides to help developers prepare for the transition.
51107

52108
### Motivation: Better Handling of Inheritance Triangles & Typeclasses
53109

54-
Why change the priority to the `given` with the most general subtype?
55-
This adjustment makes working with inheritance triangles more
56-
intuitive.
110+
The motivation for changing the prioritization of `givens` stems from the need to make interactions within inheritance hierarchies, particularly inheritance triangles, more intuitive. This adjustment addresses a common issue where the compiler struggles with ambiguity in complex typeclass hierarchies.
57111

58-
For example, functional programmers will recognize the following
59-
inheritance triangle of common typeclasses:
112+
Consider the following common inheritance triangle in functional programming:
60113

61114
```scala
62115
trait Functor[F[_]]:
63116
extension [A, B](x: F[A]) def map(f: A => B): F[B]
64117
trait Monad[F[_]] extends Functor[F] { ... }
65118
trait Traverse[F[_]] extends Functor[F] { ... }
66119
```
120+
67121
Now, suppose we have corresponding instances of these typeclasses for `List`:
122+
68123
```scala
69124
given Functor[List] = ...
70125
given Monad[List] = ...
71126
given Traverse[List] = ...
72127
```
128+
73129
Let’s use these in the following context:
130+
74131
```scala
75132
def fmap[F[_] : Functor, A, B](c: F[A])(f: A => B): F[B] = c.map(f)
76133

77134
fmap(List(1,2,3))(_.toString)
78135
// ^ rejected by Scala < 3.7, now accepted by Scala 3.7
79136
```
80137

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.
138+
Before Scala 3.7, the compiler would reject the `fmap` call due to ambiguity. The issue arose because the compiler prioritized the `given` instance with the most specific subtype of the context bound `Functor`. Both `Monad[List]` and `Traverse[List]` were valid candidates for `Functor[List]`, but neither was more specific than the other. However, all that’s required is the functionality of `Functor[List]`, the _most general_ instance, which Scala 3.7 now correctly picks.
88139

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.
140+
This change aligns compiler behavior with developers' expectations, making it easier to work with common inheritance patterns without encountering unnecessary ambiguity.
141+
142+
### Source Incompatibility of the New Givens Prioritization
143+
144+
While the new `given` prioritization improves predictability, it may affect source compatibility in existing Scala codebases. Let’s consider an example where a library provides a default `given` for a component:
145+
146+
```scala
147+
// library code
148+
class LibComponent:
149+
def msg = "library-defined"
150+
151+
// default provided by library
152+
given libComponent: LibComponent = LibComponent()
153+
154+
def printComponent(using c: LibComponent) = println(c.msg)
155+
```
156+
157+
Up until Scala 3.6, clients of the library could override `libComponent` with a user-defined one through subtyping:
158+
159+
```scala
160+
// client code
161+
class UserComponent extends LibComponent:
162+
override def msg = "user-defined"
163+
164+
given userComponent: UserComponent = UserComponent()
165+
166+
@main def run = printComponent
167+
```
168+
169+
Now, let’s run the example:
170+
171+
```scala
172+
run // Scala <= 3.6: prints "user-defined"
173+
// Scala 3.7: prints "library-defined"
174+
```
175+
176+
What happened? In Scala 3.6 and earlier, the compiler prioritized the `given` with the _most specific_ compatible subtype (`userComponent`). However, in Scala 3.7, it selects the value with the _most general_ subtype instead (`libComponent`).
177+
178+
This shift in prioritization can lead to unexpected changes in
179+
behavior when migrating to Scala 3.7, requiring developers to review
180+
and potentially adjust their codebases to ensure compatibility with
181+
the new `given` resolution logic. Below, we provide some tips to help
182+
with the migration process.
92183

93184
## Migrating to the New Prioritization
94185

0 commit comments

Comments
 (0)