Scala 3.0.0 is scheduled for release in early-mid 2021, and the first release candidate is now available. Scala 3 incorporates many changes and is based on Dotty, a new compiler using the internal data structures of Document Object Types (DOT). Scala 3 is a backward binary compatible version with Scala 2.13.
The Dotty code base is almost half the size of the current nsc
compiler and promises an improvement in performance. In development for the past eight years, new features in Dotty include new types, improved enum
handling and metaprogramming.
The timeline for the Scala 3 release can be seen in the following diagram from the Scala Lang "Crossing the Finishing Line" blog post.
As stated on the website, the development of Scala 3 is focused on three main goals:
- Strengthen Scala's foundations. Make the full programming language compatible with the foundational work on the DOT calculus and apply the lessons learned from that work.
- Make Scala easier and safer to use. Tame powerful constructs such as implicits to provide a gentler learning curve. Remove warts and puzzlers.
- Further improve the consistency and expressiveness of Scala's language constructs.
Scala 3 offers support to ease the migration from version 2. A migration guide is available and the compiler can automatically convert existing code to use some of the new Scala 3 features.
One of the new features is improved enum
support. Traditionally defining an enum
in Scala 2 is verbose. Consider the following example:
sealed trait Direction
case object North extends Direction
case object South extends Direction
Scala 3 introduces a more concise syntax with enum types
:
enum Direction:
case: North, South
Curly braces in Scala define group expressions:
sealed trait Employee {
def number: String
}
The new significant indentation based (SIB) syntax allows developers to replace the curly braces and the Dotty compiler can automatically convert existing Scala code to the new SIB syntax. When compiling with dotc -indent -rewrite
, the original Employee
sealed trait is converted to:
sealed trait Employee
def number: String
It’s also possible to revert back to the original syntax with the command dotc -no-indent -rewrite
.
The new control syntax is another syntax change specifically designed for control expressions. For instance, until now, conditions for if
statements and for
loop enumerations were enclosed in parentheses:
if (i < 3) println("smaller") else println("bigger")
for (i <- 0 until 3) println(i)
With dotc -new-syntax -rewrite
, these control expressions are rewritten without parentheses by using the then
and do
keywords:
if i < 3 then println("smaller") else println("bigger")
for i <- 0 until 3 do println(i)
As with SIB, it’s possible to revert back to the old syntax by using dotc -old-syntax -rewrite
. The compiler is able to automatically convert to the new SIB control syntaxes. However, those are separate steps, which cannot be executed at once.
Inlining is a new metaprogramming feature to achieve better performance during compile time. Scala 3 introduces the inline
soft keyword which guarantees inlining is executed instead of being a best-effort. This is different when using the final
keyword, which is based on a best-effort. After a constant is inlined, then other constants based on the inlined constant are computed compile-time:
inline val answerToEverything = 42
val doubleTheAnswerToEverything = 2 * answerToEverything
Inlining a method is a new compiler feature that replaces the method call with the body of that method. Better performance is achieved as this eliminates the need for the method to push all of its parameters to the stack. The same inline
soft keyword is used to inline a method:
inline def print(s: String): Unit =
It’s possible to further improve the performance by inlining parameters, conditionals and method overriding.
Inlining is used by Scala 3 Macros which replaces the experimental macros of Scala 2. Macros treat programs as data and manipulate them.
Several new types have been introduced in Scala 3. For instance, the &
operator is used on types to create an intersection type. The intersection type Plane & Car
contains all the members of Plane
and all the members of Car
as shown in the following example:
trait Car:
def drive = "Driving"
trait Plane:
def fly = "Flying"
def print(v: Plane & Car): Unit =
println(v.drive)
println(v.fly)
Union types use the |
operator. The union type Person | Company
contains all values of Person
and all values of Company
as shown in the following example:
def isPersonOrCompany(t: Person|Company) = t match
case p: Person(firstName) => findFirstName(firstName)
case c: Company(registrationNumber) => findRegistrationNumber(registrationNumber)
Some features have been removed in Scala 3. For instance, the Symbol
literal should be replaced by a plain String
literal or a dedicated class. Also, the new control syntax has changed the meaning of the do
keyword. The original construction of do <body> while (<cond>)
should be replaced with while ({ <body>; <cond> }) ()
.
More details on Scala 3changes and new features can be found in the documentation. A complete overview is also available on the Scala 3 website.