From 6b864524b689eb3e5bf8faa8cf6b7d046bb92790 Mon Sep 17 00:00:00 2001 From: Oliver Bracevac Date: Thu, 1 Aug 2024 11:43:01 +0200 Subject: [PATCH 01/13] Blog post about given prioritization changes --- .../2024-08-01-given-priority-change-3.5.md | 128 ++++++++++++++++++ 1 file changed, 128 insertions(+) create mode 100644 _posts/2024-08-01-given-priority-change-3.5.md diff --git a/_posts/2024-08-01-given-priority-change-3.5.md b/_posts/2024-08-01-given-priority-change-3.5.md new file mode 100644 index 000000000..8c1d625b8 --- /dev/null +++ b/_posts/2024-08-01-given-priority-change-3.5.md @@ -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: +```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 libComponent) +``` + +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(libComponent) +... +``` + +### Outlook + +We are considering adding `-rewrite` rules that automatically insert +explicit parameters when a change in choice is detected. From 66dc2a73d0a30d1363f203d3b212d42825135d0a Mon Sep 17 00:00:00 2001 From: Oliver Bracevac Date: Thu, 1 Aug 2024 11:50:32 +0200 Subject: [PATCH 02/13] Tweak --- _posts/2024-08-01-given-priority-change-3.5.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/_posts/2024-08-01-given-priority-change-3.5.md b/_posts/2024-08-01-given-priority-change-3.5.md index 8c1d625b8..14e059e0a 100644 --- a/_posts/2024-08-01-given-priority-change-3.5.md +++ b/_posts/2024-08-01-given-priority-change-3.5.md @@ -106,7 +106,7 @@ This will issue warnings when the choice of `given` has changed: If the pre-3.5 behavior is preferred, you can explicitly pass the desired given: ```scala -@main def run = printComponent(using libComponent) +@main def run = printComponent(using userComponent) ``` To determine the correct explicit parameter (which could involve a @@ -118,7 +118,7 @@ scalac client.scala -Xprint:typer This will output all parameters explicitly: ```scala ... -@main def run: Unit = printComponent(libComponent) +@main def run: Unit = printComponent(userComponent) ... ``` From 67fc9a0abf514d44c5ed55b2c1ddf87a42c120c8 Mon Sep 17 00:00:00 2001 From: Oliver Bracevac Date: Thu, 1 Aug 2024 16:36:31 +0200 Subject: [PATCH 03/13] Update blog post * Clarify compiler behavior and compiler options * Add discussion on changes to context bounds --- .../2024-08-01-given-priority-change-3.5.md | 90 ++++++++++++++++--- 1 file changed, 77 insertions(+), 13 deletions(-) diff --git a/_posts/2024-08-01-given-priority-change-3.5.md b/_posts/2024-08-01-given-priority-change-3.5.md index 14e059e0a..1cd97051f 100644 --- a/_posts/2024-08-01-given-priority-change-3.5.md +++ b/_posts/2024-08-01-given-priority-change-3.5.md @@ -2,16 +2,17 @@ layout: blog-detail post-type: blog by: Oliver Bračevac, EPFL -title: "Changes to Givens Prioritization in Scala 3.5" +title: "Changes to Givens in Scala 3.5" --- -## Motivation +## New Prioritization of Givens 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: +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] @@ -40,19 +41,24 @@ than the other. However, all we really need is the functionality of 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]`. +case, it chooses the desired `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 +### Community Impact 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. +changes due to the new prioritization of givens. Cf. below for +tips to migrate to Scala 3.5. + + +## Tips for Migrating to the New Prioritization + In some cases, the new prioritization might silently select the wrong `given`. For example, consider a library that provides a default @@ -82,12 +88,9 @@ given userComponent: UserComponent = UserComponent() // 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: -```bash -scalac client.scala -source:3.6-migration -``` -This will issue warnings when the choice of `given` has changed: +Scala 3.5 will automatically issue +warnings when the choice of `given` has changed: + ```scala -- Warning: client.scala:11:30 ------------------------------------------ 11 |@main def run = printComponent @@ -101,7 +104,20 @@ This will issue warnings when the choice of `given` has changed: | New choice from Scala 3.6: the second alternative ``` -### Explicit Parameters + +### Useful Compiler Options + +In future releases (Scala 3.6+), automatic warnings related to changes +in the selection of givens, as described above, will no longer be +issued by default. However, these warnings can be reactivated using +the `-source:3.5` option with `scalac`. + +Additionally, combining Scala 3.5 with the `-source:3.6` option can be +useful to verify that implicit search results will not be ambiguous in +future versions or to test your application at runtime with the new +rules in effect. + +### Resorting to Explicit Parameters If the pre-3.5 behavior is preferred, you can explicitly pass the desired given: @@ -126,3 +142,51 @@ This will output all parameters explicitly: We are considering adding `-rewrite` rules that automatically insert explicit parameters when a change in choice is detected. + + +## Towards Context Bounds as Givens + +We are gradually phasing out remaining uses of Scala 2 style +`implicit`s in favor of the `given`/`using` syntax. Scala 3.5 marks +the first step in transitioning context bounds on type parameters to +givens, with this transition expected to be completed in the upcoming +Scala 3.6 release. + +Currently, context bounds on type parameters still desugar into +`implicit` parameters: + +```scala +def f[Element : Eq : Ordering] = summon[Eq[Element]].toOrdering +// expands to: +def f[Element >: Nothing <: Any](implicit evidence$1: Eq[Element], + implicit evidence$2: Order[Element]): Ordering[Element] = + evidence$2.toOrdering +``` + +Prior to Scala 3.5, it was possible to pass `implicit` arguments +explicitly for context bounds as if they were regular arguments. In +Scala 3.5, however, these parameters must be qualified with `using`: + +```scala +val eq: Eq[Int] = ??? +val ord: Order[Int] = ??? +f(eq, ord) // ok in Scala < 3.5, error in 3.5 +f(using eq, ord) // ok in Scala 3.5 +``` + +At this stage, the change does not affect the expansion of functions +like `f` above, which still rely on `implicit` parameters. However, +this is a crucial step towards facilitating the eventual transition to +`given`s for context bounds in Scala 3.6. + +To assist with the migration to explicit `using` clauses, Scala 3.5 +provides an error message and offers automatic rewrites: + +```scala +-- Error: bounds.scala:10:2 ---------------------------------------------- +10 | f(eq, ord) // error + | ^ + |Context bounds will map to context parameters. + |A `using` clause is needed to pass explicit arguments to them. + |This code can be rewritten automatically under -rewrite -source 3.4-migration. +``` From 0319c0b12203cefc89996f8b2185eb2fb1ba19b7 Mon Sep 17 00:00:00 2001 From: Oliver Bracevac Date: Thu, 1 Aug 2024 19:36:08 +0200 Subject: [PATCH 04/13] Discuss explicit prioritization by owner --- .../2024-08-01-given-priority-change-3.5.md | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/_posts/2024-08-01-given-priority-change-3.5.md b/_posts/2024-08-01-given-priority-change-3.5.md index 1cd97051f..0aecae30a 100644 --- a/_posts/2024-08-01-given-priority-change-3.5.md +++ b/_posts/2024-08-01-given-priority-change-3.5.md @@ -138,6 +138,35 @@ This will output all parameters explicitly: ... ``` +### Explicit Prioritization by Owner + +One effective way to ensure that the most specific given instance is +selected -— particularly useful when migrating libraries to Scala 3.5 -— +is to leverage the inheritance rules as outlined in point 8 of [the +language +reference](https://docs.scala-lang.org/scala3/reference/changed-features/implicit-resolution.html): + +```scala +class General +class Specific extends General + +class LowPriority: + given a:General() + +object NormalPriority extends LowPriority: + given b:Specific() + +def run = + import NormalPriority.given + val x = summon[General] + val _: Specific = x // <- b was picked +``` + +The idea is to enforce prioritization through the inheritance +hierarchies of classes that provide `given` instances. By importing the +`given` instances from the object with the highest priority, you can +control which instance is selected by the compiler. + ### Outlook We are considering adding `-rewrite` rules that automatically insert From 41a85c99687155724154e78edb90682a55d8c0fa Mon Sep 17 00:00:00 2001 From: Oliver Bracevac Date: Fri, 2 Aug 2024 17:02:02 +0200 Subject: [PATCH 05/13] Remark on possible spurious warnings in explicit prioritization --- _posts/2024-08-01-given-priority-change-3.5.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/_posts/2024-08-01-given-priority-change-3.5.md b/_posts/2024-08-01-given-priority-change-3.5.md index 0aecae30a..739ef48fb 100644 --- a/_posts/2024-08-01-given-priority-change-3.5.md +++ b/_posts/2024-08-01-given-priority-change-3.5.md @@ -167,6 +167,13 @@ hierarchies of classes that provide `given` instances. By importing the `given` instances from the object with the highest priority, you can control which instance is selected by the compiler. +**Note**: Due to the release cut-off, Scala 3.5.0 may still report a +warning that the above pattern will be ambiguous in Scala +3.6. However, this warning is incorrect. The issue has already been +resolved, and the improvements will be included in an upcoming bugfix +release. + + ### Outlook We are considering adding `-rewrite` rules that automatically insert From 024f8289bd656927e79f590b08f3a6676cf5e04a Mon Sep 17 00:00:00 2001 From: Oliver Bracevac Date: Fri, 2 Aug 2024 19:19:02 +0200 Subject: [PATCH 06/13] Discuss how to suppress the warning --- .../2024-08-01-given-priority-change-3.5.md | 37 +++++++++++++++++-- 1 file changed, 34 insertions(+), 3 deletions(-) diff --git a/_posts/2024-08-01-given-priority-change-3.5.md b/_posts/2024-08-01-given-priority-change-3.5.md index 739ef48fb..b24e9112b 100644 --- a/_posts/2024-08-01-given-priority-change-3.5.md +++ b/_posts/2024-08-01-given-priority-change-3.5.md @@ -117,6 +117,35 @@ useful to verify that implicit search results will not be ambiguous in future versions or to test your application at runtime with the new rules in effect. +#### Suppressing Warnings + +If you need to suppress the new warning related to changes in `given` +search preference, you can use Scala’s facilities for configuring +warnings. For example, you can suppress the warning globally via the +command line: + +```bash +scalac file.scala "-Wconf:msg=Given search preference:s" +``` + +It is also possible to selectively suppress the warning +using the [`@nowarn` annotation](https://www.scala-lang.org/api/current/scala/annotation/nowarn.html): + +```scala +import scala.annotation.nowarn + +class A +class B extends A + +given A() +given B() + +@nowarn("msg=Given search preference") +val x = summon[A] +``` + +For more details, you can consult the guide on [configuring and suppressing warnings]({{ site.baseurl }}/2021/01/12/configuring-and-suppressing-warnings.html). + ### Resorting to Explicit Parameters If the pre-3.5 behavior is preferred, you can explicitly pass the @@ -167,11 +196,13 @@ hierarchies of classes that provide `given` instances. By importing the `given` instances from the object with the highest priority, you can control which instance is selected by the compiler. -**Note**: Due to the release cut-off, Scala 3.5.0 may still report a +#### Upcoming Bugfixes + + Due to the release cut-off, Scala 3.5.0 may still report a warning that the above pattern will be ambiguous in Scala 3.6. However, this warning is incorrect. The issue has already been -resolved, and the improvements will be included in an upcoming bugfix -release. +resolved, and an upcoming bugfix release will improve the +accuracy of warnings. ### Outlook From 7e7d493405b36f3a3eabe521d4b5a45895a54c5a Mon Sep 17 00:00:00 2001 From: Oliver Bracevac Date: Sat, 17 Aug 2024 00:04:47 +0200 Subject: [PATCH 07/13] Update blog post --- .../2024-08-01-given-priority-change-3.5.md | 237 ++++++++---------- 1 file changed, 105 insertions(+), 132 deletions(-) diff --git a/_posts/2024-08-01-given-priority-change-3.5.md b/_posts/2024-08-01-given-priority-change-3.5.md index b24e9112b..ed73ccda9 100644 --- a/_posts/2024-08-01-given-priority-change-3.5.md +++ b/_posts/2024-08-01-given-priority-change-3.5.md @@ -2,94 +2,116 @@ layout: blog-detail post-type: blog by: Oliver Bračevac, EPFL -title: "Changes to Givens in Scala 3.5" +title: "Upcoming Changes to Givens in Scala 3.7" --- -## New Prioritization of Givens +## New Prioritization of Givens in Scala 3.7 -Starting with Scala 3.5, the prioritization of givens has been -improved to better handle inheritance triangles, resulting in enhanced -typeclass support. +Scala 3.7 introduces changes to how `givens` are resolved, which can +affect program behavior when multiple givens are present. + +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) +``` + +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 +``` + +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 prioritizes the +`give`n with the _most specific_ compatible subtype +(`userComponent`). However, in Scala 3.7, it selects the value with the +_most general_ subtype instead (`libComponent`). + + +### 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. + +For example, functional programmers will recognize the following +inheritance triangle of common typeclasses: -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: +Now, suppose we have corresponding instances of these typeclasses for `List`: ```scala -given a:Functor[List] = ... -given b:Monad[List] = ... -given c:Traverse[List] = ... +given Functor[List] = ... +given Monad[List] = ... +given Traverse[List] = ... ``` -Now, let’s use these in the following context: +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 +// ^ rejected by Scala < 3.7, now accepted by Scala 3.7 ``` -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]`! +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. -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 the desired `a:Functor[List]`. +This change aligns the behavior of the compiler with the practical +needs of developers, making the handling of common triangle inheritance +patterns more predictable. -Inheritance triangles like this are common in practice, and the -prioritization change in Scala 3.5 makes working with them more -intuitive and straightforward. +## Migrating to the New Prioritization ### Community Impact -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. Cf. below for -tips to migrate to Scala 3.5. - - -## Tips for Migrating to the New Prioritization - - -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) -``` +We have conducted experiments on the [open community +build](https://github.com/VirtusLab/community-build3) that showed that +the proposed scheme will result in a more intuitive and predictable +given resolution. The negative impact on the existing projects is very +small. We have tested 1500 open-source libraries, and new rules are +causing problems for less than a dozen of them. -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" +### Roadmap -given userComponent: UserComponent = UserComponent() +The new `given` resolution scheme, which will be the default in Scala +3.7, can already be explored in Scala 3.5. This early access allows +the community ample time to test and adapt to the upcoming changes. -@main def run = printComponent -// Scala < 3.5: prints "user-defined" -// Scala 3.5: prints "library-defined" -``` +**Scala 3.5** -Scala 3.5 will automatically issue -warnings when the choice of `given` has changed: +Starting with Scala 3.5, you can compile with `-source 3.6` to receive +warnings if the new `given` resolution scheme would affect your +code. This is how the warning might look: ```scala -- Warning: client.scala:11:30 ------------------------------------------ @@ -101,21 +123,21 @@ warnings when the choice of `given` has changed: | (libComponent : LibComponent) | has changed. | Previous choice : the first alternative - | New choice from Scala 3.6: the second alternative + | New choice from Scala 3.7: the second alternative ``` +Additionally, you can compile with `-source 3.7` or `-source future` +to fully enable the new prioritization and start experiencing its +effects. + +**Scala 3.6** -### Useful Compiler Options +In Scala 3.6, these warnings will be on by default. -In future releases (Scala 3.6+), automatic warnings related to changes -in the selection of givens, as described above, will no longer be -issued by default. However, these warnings can be reactivated using -the `-source:3.5` option with `scalac`. +**Scala 3.7** -Additionally, combining Scala 3.5 with the `-source:3.6` option can be -useful to verify that implicit search results will not be ambiguous in -future versions or to test your application at runtime with the new -rules in effect. +Scala 3.7 will finalize the transition, making the new given +prioritization the standard behavior. #### Suppressing Warnings @@ -146,17 +168,22 @@ val x = summon[A] For more details, you can consult the guide on [configuring and suppressing warnings]({{ site.baseurl }}/2021/01/12/configuring-and-suppressing-warnings.html). -### Resorting to Explicit Parameters +### Workarounds -If the pre-3.5 behavior is preferred, you can explicitly pass the +Here are some practical strategies to help you smoothly adapt to the +new given resolution scheme: + +#### Resorting to Explicit Parameters + +If the pre-3.7 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: +complex expression), it can be helpful to compile with an earlier +Scala version using the `-Xprint:typer` flag: ```scala scalac client.scala -Xprint:typer ``` @@ -167,10 +194,10 @@ This will output all parameters explicitly: ... ``` -### Explicit Prioritization by Owner +#### Explicit Prioritization by Owner One effective way to ensure that the most specific given instance is -selected -— particularly useful when migrating libraries to Scala 3.5 -— +selected -— particularly useful when migrating libraries to Scala 3.7 -— is to leverage the inheritance rules as outlined in point 8 of [the language 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 `given` instances from the object with the highest priority, you can control which instance is selected by the compiler. -#### Upcoming Bugfixes - - Due to the release cut-off, Scala 3.5.0 may still report a -warning that the above pattern will be ambiguous in Scala -3.6. However, this warning is incorrect. The issue has already been -resolved, and an upcoming bugfix release will improve the -accuracy of warnings. - - ### Outlook We are considering adding `-rewrite` rules that automatically insert explicit parameters when a change in choice is detected. -## Towards Context Bounds as Givens - -We are gradually phasing out remaining uses of Scala 2 style -`implicit`s in favor of the `given`/`using` syntax. Scala 3.5 marks -the first step in transitioning context bounds on type parameters to -givens, with this transition expected to be completed in the upcoming -Scala 3.6 release. - -Currently, context bounds on type parameters still desugar into -`implicit` parameters: - -```scala -def f[Element : Eq : Ordering] = summon[Eq[Element]].toOrdering -// expands to: -def f[Element >: Nothing <: Any](implicit evidence$1: Eq[Element], - implicit evidence$2: Order[Element]): Ordering[Element] = - evidence$2.toOrdering -``` - -Prior to Scala 3.5, it was possible to pass `implicit` arguments -explicitly for context bounds as if they were regular arguments. In -Scala 3.5, however, these parameters must be qualified with `using`: - -```scala -val eq: Eq[Int] = ??? -val ord: Order[Int] = ??? -f(eq, ord) // ok in Scala < 3.5, error in 3.5 -f(using eq, ord) // ok in Scala 3.5 -``` -At this stage, the change does not affect the expansion of functions -like `f` above, which still rely on `implicit` parameters. However, -this is a crucial step towards facilitating the eventual transition to -`given`s for context bounds in Scala 3.6. - -To assist with the migration to explicit `using` clauses, Scala 3.5 -provides an error message and offers automatic rewrites: - -```scala --- Error: bounds.scala:10:2 ---------------------------------------------- -10 | f(eq, ord) // error - | ^ - |Context bounds will map to context parameters. - |A `using` clause is needed to pass explicit arguments to them. - |This code can be rewritten automatically under -rewrite -source 3.4-migration. -``` From 2f017ac429c3b0ae790b07398a3bfb4f15b25cc0 Mon Sep 17 00:00:00 2001 From: Oliver Bracevac Date: Sat, 17 Aug 2024 00:07:39 +0200 Subject: [PATCH 08/13] Rename the file --- ...rity-change-3.5.md => 2024-08-19-given-priority-change-3.7.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename _posts/{2024-08-01-given-priority-change-3.5.md => 2024-08-19-given-priority-change-3.7.md} (100%) diff --git a/_posts/2024-08-01-given-priority-change-3.5.md b/_posts/2024-08-19-given-priority-change-3.7.md similarity index 100% rename from _posts/2024-08-01-given-priority-change-3.5.md rename to _posts/2024-08-19-given-priority-change-3.7.md From f570f20875f81e4398a03f476217118113c74cf5 Mon Sep 17 00:00:00 2001 From: Oliver Bracevac Date: Mon, 19 Aug 2024 20:09:55 +0200 Subject: [PATCH 09/13] Typos --- _posts/2024-08-19-given-priority-change-3.7.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/_posts/2024-08-19-given-priority-change-3.7.md b/_posts/2024-08-19-given-priority-change-3.7.md index ed73ccda9..ba5fefcf2 100644 --- a/_posts/2024-08-19-given-priority-change-3.7.md +++ b/_posts/2024-08-19-given-priority-change-3.7.md @@ -7,8 +7,8 @@ title: "Upcoming Changes to Givens in Scala 3.7" ## New Prioritization of Givens in Scala 3.7 -Scala 3.7 introduces changes to how `givens` are resolved, which can -affect program behavior when multiple givens are present. +Scala 3.7 will introduce changes to how `given`s are resolved, which can +affect program behavior when multiple `given`s are present. For example, consider a library that provides a default `given` for a component: @@ -44,7 +44,7 @@ run // Scala <= 3.6: prints "user-defined" ``` What happened? In Scala 3.6 and earlier, the compiler prioritizes the -`give`n with the _most specific_ compatible subtype +`given` with the _most specific_ compatible subtype (`userComponent`). However, in Scala 3.7, it selects the value with the _most general_ subtype instead (`libComponent`). From 8bf4e31d7e5526f70105cc83f5728062c3974615 Mon Sep 17 00:00:00 2001 From: Oliver Bracevac Date: Mon, 19 Aug 2024 20:16:20 +0200 Subject: [PATCH 10/13] Warning about ... warnings --- _posts/2024-08-19-given-priority-change-3.7.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/_posts/2024-08-19-given-priority-change-3.7.md b/_posts/2024-08-19-given-priority-change-3.7.md index ba5fefcf2..b5d77ac29 100644 --- a/_posts/2024-08-19-given-priority-change-3.7.md +++ b/_posts/2024-08-19-given-priority-change-3.7.md @@ -168,6 +168,13 @@ val x = summon[A] For more details, you can consult the guide on [configuring and suppressing warnings]({{ site.baseurl }}/2021/01/12/configuring-and-suppressing-warnings.html). +**Caution**: Suppressing warnings should be viewed as a temporary +workaround, not a long-term solution. While it can help address rare +false positives from the compiler, it merely postpones the inevitable +need to update your codebase or the libraries your project depends +on. Relying on suppressed warnings may lead to unexpected issues when +upgrading to future versions of the Scala compiler. + ### Workarounds Here are some practical strategies to help you smoothly adapt to the From 2adf42d7bc4d0d1d6464432c14f176d7d1c0b88d Mon Sep 17 00:00:00 2001 From: Oliver Bracevac Date: Mon, 19 Aug 2024 20:34:54 +0200 Subject: [PATCH 11/13] Change structure Open with the motivation first, then with the negative part. --- .../2024-08-19-given-priority-change-3.7.md | 127 +++++++++++++++--- 1 file changed, 109 insertions(+), 18 deletions(-) diff --git a/_posts/2024-08-19-given-priority-change-3.7.md b/_posts/2024-08-19-given-priority-change-3.7.md index b5d77ac29..36bf45424 100644 --- a/_posts/2024-08-19-given-priority-change-3.7.md +++ b/_posts/2024-08-19-given-priority-change-3.7.md @@ -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 @@ -48,15 +99,17 @@ 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[_]]: @@ -64,13 +117,17 @@ trait Functor[F[_]]: 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) @@ -78,17 +135,51 @@ 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 From 0610d5e8c832daa85e24fb6543070c8663b81edf Mon Sep 17 00:00:00 2001 From: Oliver Bracevac Date: Tue, 20 Aug 2024 11:28:43 +0200 Subject: [PATCH 12/13] Remove an orphan --- _posts/2024-08-19-given-priority-change-3.7.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/_posts/2024-08-19-given-priority-change-3.7.md b/_posts/2024-08-19-given-priority-change-3.7.md index 36bf45424..3e3aae1d6 100644 --- a/_posts/2024-08-19-given-priority-change-3.7.md +++ b/_posts/2024-08-19-given-priority-change-3.7.md @@ -99,8 +99,6 @@ 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. From fd42c3681b689934af68474db9e46ac36117a317 Mon Sep 17 00:00:00 2001 From: Oliver Bracevac Date: Tue, 20 Aug 2024 11:38:50 +0200 Subject: [PATCH 13/13] Deduplicate accidental duplication --- .../2024-08-19-given-priority-change-3.7.md | 102 +++--------------- 1 file changed, 15 insertions(+), 87 deletions(-) diff --git a/_posts/2024-08-19-given-priority-change-3.7.md b/_posts/2024-08-19-given-priority-change-3.7.md index 3e3aae1d6..15438c3a7 100644 --- a/_posts/2024-08-19-given-priority-change-3.7.md +++ b/_posts/2024-08-19-given-priority-change-3.7.md @@ -60,86 +60,10 @@ 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 -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 -``` - -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 prioritizes 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`). - -## 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 - -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. - -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. 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 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: +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 @@ -152,7 +76,8 @@ 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: +Up until Scala 3.6, clients of the library could override +`libComponent` with a user-defined one through subtyping: ```scala // client code @@ -171,7 +96,10 @@ 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`). +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 @@ -186,7 +114,7 @@ with the migration process. We have conducted experiments on the [open community build](https://github.com/VirtusLab/community-build3) that showed that the proposed scheme will result in a more intuitive and predictable -given resolution. The negative impact on the existing projects is very +`given` resolution. The negative impact on the existing projects is very small. We have tested 1500 open-source libraries, and new rules are causing problems for less than a dozen of them. @@ -225,7 +153,7 @@ In Scala 3.6, these warnings will be on by default. **Scala 3.7** -Scala 3.7 will finalize the transition, making the new given +Scala 3.7 will finalize the transition, making the new `given` prioritization the standard behavior. #### Suppressing Warnings @@ -267,12 +195,12 @@ upgrading to future versions of the Scala compiler. ### Workarounds Here are some practical strategies to help you smoothly adapt to the -new given resolution scheme: +new `given` resolution scheme: #### Resorting to Explicit Parameters If the pre-3.7 behavior is preferred, you can explicitly pass the -desired given: +desired `given`: ```scala @main def run = printComponent(using userComponent) ``` @@ -292,7 +220,7 @@ This will output all parameters explicitly: #### Explicit Prioritization by Owner -One effective way to ensure that the most specific given instance is +One effective way to ensure that the most specific `given` instance is selected -— particularly useful when migrating libraries to Scala 3.7 -— is to leverage the inheritance rules as outlined in point 8 of [the language