Scala 3 Migration Guide

Kind Projector Migration

Language

In the future, Scala 3 will use the _ underscore symbol for placeholders in type lambdas—just as the underscore is currently used for placeholders in (ordinary) term-level lambdas.

The new type lambda syntax is not enabled by default, to enable it, use a compiler flag -Ykind-projector:underscores. Note that enabling underscore type lambdas will disable usage of _ as a wildcard, you will only be able to write wildcards using the ? symbol.

If you wish to cross-compile a project for Scala 2 & Scala 3 while using underscore type lambdas for both, you may do so starting with kind-projector version 0.13.0 and up and Scala 2 versions 2.13.6 and 2.12.14. To enable it, add the compiler flags -Xsource:3 -P:kind-projector:underscore-placeholders to your build. As in Scala 3, this will disable usage of _ as a wildcard, however, the flag -Xsource:3 will allow you to replace it with the ? symbol.

The following sbt configuration will set up the correct flags to cross-compile with new syntax:

ThisBuild / scalacOptions ++= {
  CrossVersion.partialVersion(scalaVersion.value) match {
    case Some((3, _)) => Seq("-Ykind-projector:underscores")
    case Some((2, 12 | 13)) => Seq("-Xsource:3", "-P:kind-projector:underscore-placeholders")
  }
}

Migrating to New Syntax

To use underscores for type-lambdas in existing kind-projector enabled code, replace * or ? type lambda placeholders with _.

In turn, you will also have to rewrite all usages of _ as the wildcard to use ? symbol.

For example the following usage of the wildcard:

def getWidget(widgets: Set[_ <: Widget], name: String): Option[Widget] =
  widgets.find(_.name == name)

Must be rewritten to:

def getWidget(widgets: Set[? <: Widget], name: String): Option[Widget] =
  widgets.find(_.name == name)

And the following usages of kind-projector’s * placeholder:

Tuple2[*, Double]        // equivalent to: type R[A] = Tuple2[A, Double]
Either[Int, +*]          // equivalent to: type R[+A] = Either[Int, A]
Function2[-*, Long, +*]  // equivalent to: type R[-A, +B] = Function2[A, Long, B]

Must be rewritten to:

Tuple2[_, Double]        // equivalent to: type R[A] = Tuple2[A, Double]
Either[Int, +_]          // equivalent to: type R[+A] = Either[Int, A]
Function2[-_, Long, +_]  // equivalent to: type R[-A, +B] = Function2[A, Long, B]

Compiling Existing Code

Even without migrating to underscore type lambdas, you will likely be able to compile most of it with Scala 3 without changes.

Use the flag -Ykind-projector to enable support for *-based type lambdas (without enabling underscore type lambdas), the following forms will now compile:

Tuple2[*, Double]        // equivalent to: type R[A] = Tuple2[A, Double]
Either[Int, +*]          // equivalent to: type R[+A] = Either[Int, A]
Function2[-*, Long, +*]  // equivalent to: type R[-A, +B] = Function2[A, Long, B]

Rewriting Incompatible Constructs

Scala 3’s -Ykind-projector & -Ykind-projector:underscores implement only a subset of kind-projector syntax, in particular they do not implement:

  • higher-kinded type lambda placeholders
  • higher-kinded named type lambda parameters
  • The Lambda keyword (λ is still supported)

You must rewrite ALL of the following forms:

// classic
EitherT[*[_], Int, *]    // equivalent to: type R[F[_], B] = EitherT[F, Int, B]
// underscores
EitherT[_[_], Int, _]    // equivalent to: type R[F[_], B] = EitherT[F, Int, B]
// named λ
λ[(F[_], A) => EitherT[F, Int, A]]
// named Lambda
Lambda[(F[_], A) => EitherT[F, Int, A]]

Into the following long-form to cross-compile with Scala 3:

type MyLambda[F[_], A] = EitherT[F, Int, A]
MyLambda

Alternatively you may use Scala 3’s Native Type Lambdas if you do not need to cross-compile:

[F[_], A] =>> EitherT[F, Int, A]

For Lambda you must rewrite the following form:

Lambda[(`+E`, `+A`) => Either[E, A]]

To the following to cross-compile:

λ[(`+E`, `+A`) => Either[E, A]]

Or alternatively to Scala 3 type lambdas:

[E, A] =>> Either[E, A]

Note: Scala 3 type lambdas no longer need - or + variance markers on parameters, these are now inferred.

Contributors to this page: