Skip to content

Commit

Permalink
Change structure
Browse files Browse the repository at this point in the history
Open with the motivation first, then
with the negative part.
  • Loading branch information
bracevac committed Aug 19, 2024
1 parent 8bf4e31 commit 2adf42d
Showing 1 changed file with 109 additions and 18 deletions.
127 changes: 109 additions & 18 deletions _posts/2024-08-19-given-priority-change-3.7.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,61 @@ title: "Upcoming Changes to Givens in Scala 3.7"

## New Prioritization of Givens in Scala 3.7

Scala 3.7 will introduce changes to how `given`s are resolved, which can
affect program behavior when multiple `given`s are present.
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 problems 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.

For example, consider a library that provides a default
### Motivation: Better Handling of Inheritance Triangles & Typeclasses

The motivation for changing the prioritization of `given`s 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.

For example, functional programmers will recognize the following
inheritance triangle of common typeclasses:

```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] { ... }
```
Now, suppose we have corresponding instances of these typeclasses for `List`:
```scala
given Functor[List] = ...
given Monad[List] = ...
given Traverse[List] = ...
```
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.7, now accepted by Scala 3.7
```

Before Scala 3.7, the compiler would reject the `fmap` call due to
ambiguity. Since it 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 instance with the _most
general_ subtype, which Scala 3.7 correctly picks.

This change aligns the behavior of the compiler with the practical
needs of developers, making the handling of common triangle
inheritance patterns more predictable.

### Source Incompatibility of the New Givens Prioritization

Unfortunately, this change might affect source compatibility of some Scala codebases.
Let's consider a library that provides a default
`given` for a component:
```scala
// library code
Expand Down Expand Up @@ -48,47 +99,87 @@ What happened? In Scala 3.6 and earlier, the compiler prioritizes the
(`userComponent`). However, in Scala 3.7, it selects the value with the
_most general_ subtype instead (`libComponent`).

Here's the revised version with improved transitions and adjustments for the reordering:

## New Prioritization of Givens in Scala 3.7

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.

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

Why change the priority to the `given` with the most general subtype?
This adjustment makes working with inheritance triangles more
intuitive.
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.

For example, functional programmers will recognize the following
inheritance triangle of common typeclasses:
Consider the following common inheritance triangle in functional programming:

```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] { ... }
```

Now, suppose we have corresponding instances of these typeclasses for `List`:

```scala
given Functor[List] = ...
given Monad[List] = ...
given Traverse[List] = ...
```

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.7, now accepted by Scala 3.7
```

Before Scala 3.7, the compiler would reject the `fmap` call due to
ambiguity. Since it 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 correctly picks.
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.

This change aligns the behavior of the compiler with the practical
needs of developers, making the handling of common triangle inheritance
patterns more predictable.
This change aligns compiler behavior with developers' expectations, making it easier to work with common inheritance patterns without encountering unnecessary ambiguity.

### Source Incompatibility of the New Givens Prioritization

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:

```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)
```

Up until Scala 3.6, clients of the library could override `libComponent` with a user-defined one through subtyping:

```scala
// client code
class UserComponent extends LibComponent:
override def msg = "user-defined"

given userComponent: UserComponent = UserComponent()

@main def run = printComponent
```

Now, let’s run the example:

```scala
run // Scala <= 3.6: prints "user-defined"
// Scala 3.7: prints "library-defined"
```

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`).

This shift in prioritization can lead to unexpected changes in
behavior when migrating to Scala 3.7, requiring developers to review
and potentially adjust their codebases to ensure compatibility with
the new `given` resolution logic. Below, we provide some tips to help
with the migration process.

## Migrating to the New Prioritization

Expand Down

0 comments on commit 2adf42d

Please sign in to comment.