Why Should I upgrade to Scala 3?

Scala 3 was launched in May 2021 with version 3.2.0 released in September 2022. However, many developers are still using Scala 2.13.8 which was released in January 2022. Why is this?

30-09-2022
Bcorp Logo


Why Should I upgrade to Scala 3?

Scala 3 was launched in May 2021 with version 3.2.0 released in September 2022. However, many developers are still using Scala 2.13.8 which was released in January 2022. Why is this?

First off it is important to understand the Scala version numbering system, unlike languages such as Java the versions 2.x and 3.x indicate an epoch in the language. This is more akin to Python and having Python 2 and Python 3. Epochs represent major changes to the language. Whereas version 2.1 and 2.12 or indeed 2.13 represent major version changes so 2.11 to 2.12 isa major version release of the Scala 2 epoch (unlike java where Java 14 and Java 15 might represent relatively small changes). Minor updates are then released as version 2.12.1 or 2.12.2 etc.

Therefore, changing from Scala 2 to Scala 3 represents change in epoch – while not a change in the language this represents a significant update to the core language and its constructs (compared to updating from Scala 2.12 to Scala 2.13). This means that more consideration must be given to the change than might at first seem to the case.

It is important to note that currently both Scala 3.2.0 and Scala 2.13.8 are supported versions of the language and that they are not considered source code compatible. Scala 2 is therefore not obsolete, but nor is Scala 2 source code directly compatible with Scala 3 source code. However, the compilers for the two versions can be made to generate Scala 3 compatible executable code and thus a codebase could be comprised of Scala 2 and Scala 3 source code.

Interestingly, in this writers’ experience, many companies are still using Scala 2, not least due to the investment they have made in it, in terms of training, tooling and of course the existing codebases they have created.

So, is Scala going the way of Python where both Python 2 and Python 3 ran alongside each other for over a decade? Possibly!

This means that you could stay just working with Scala 2 for the foreseeable future, however at some point in time you may well need to consider moving over to Scala 3, in this blog we will look at 5 reasons why you should consider moving sooner rather than later.

1. Simplified Syntax

Scala 3 is a significant overhaul of the syntax in Scala. Some of this overhaul brings Scala more in line with other languages and some of it just simplifies the language itself. We will look at a few examples to see what we mean.

Import Statements

Almost every modern programming language uses an ‘*’ (asterisk) as a wild card and many use this symbol to indicate that you are importing multiple things from a package (for example Java, JavaScript, C# to name but three). However, Scala 2 used an ‘_’ (underbar). Thus, in Scala 2 to import everything from the transport package you wrote:

import scala.util._

Whereas in Scala 3 you write:

import scala.util.*

This is not a major change but the new syntax is more intuitive to the majority of programmers moving from other languages. It also illustrates how Scala 2 and Scala 3 are not source code compatible.

Sticking with the import statement, if you wanted to use an alias for a type, in Scala two you had to use a fat arrow to associate an alias name with the original name, for example:

import java.util.{List => JList}

Again, not impossible to understand but also not necessarily the most intuitive syntax ever devised. In Scala 3 you use the key word as:

import java.util.List as
JList

Which is I think clearer and easier to read (even if you are not familiar with Scala).

Enumerations

Strictly speaking there was no special enumeration syntax in Scala 2. Instead there was a trait (which is a type that can be mixed into other types) called Enumeration. This typed could be mixed into an object (which is Scala’s built in implementation of the singleton pattern), to create a set of enumerated values. This is a type that can be used to represent an ordered sequence of values. For example,

object DaysOfWeek extends Enumeration {
  val Monday = Value
  val Tuesday = Value
  val Wednesday = Value
  val Thursday = Value
  val Friday = Value
}

This is quiet long winded and strictly speaking Monday, Tuesday etc are examples of the Value type defined within the DaysOfWeek object. None of which exactly sounds like an enumerated type.

The above is also quiet verbose and admittedly there is a shorthand from, that could have been used:

object DaysOfWeek extends Enumeration {
  val Monday, Tuesday, Wednesday, Thursday, Friday = Value
}

But its still not exactly an enumerated type as we might expect it in a language such as C# or C++.

In Scala 3 the keyword enum has been added to the language and an enumerated type can eb defined as follows:

enum DaysOfWeek:
    case Monday, Tuesday, Wednesday, Thursday, Friday

This is both more concise and more explicit as it clearly marks the definition as an enumerated type.

Why Should I upgrade to Scala 3?


Main Entry Point

In Scala 2 to indicate that some code was the entry point for an application it was necessary either to define an object containing a method with the signature def main(args: Array[String]): Unit or to mix in the trait App which then captures the free standing code defined within the body of an object as the body of the main method, for example the long hand from is:

object HelloWorldLongHand {
  def main(args: Array[String]): Unit = {
    println("Hello, World!")
  }
}

The shorter version missing in the App trait is:

object HelloWorld extends App{
    println("Hello World!")
}

However, Scala 3 has done away with the need for the definition of an object, and either the definition of the main method or the mixing in of a Trait using a <b>@main</b> decorator on a plain function, for example:

@main def main() = {
    println("Hello World!")
}

Simplification Summary

The above are just a few of the examples from Scala 3 which illustrate the ways in which the language syntax has been simplified. It should be noted that much of the language (say 80%) still remains between Scala 2 and Scala 3 and so a large amount of code in a Scala 3 application should still be understandable to a Scala 2 developer.

2. Implicits have been redesigned!

An often-criticised feature of Scala 2 were implicits. Indeed, in one project I worked on implicits were banned from use as they were considered harmful! That is not to say that implicits did not have their use, they wewre often used for providing implicit conversion classes, defining extension methods to existing types, providing contextual environment values and providing an instance of type class or classes.

The issue with implicit was that often they behaved in an apparently hidden and opaque way, resulting in developers not realising how or when they were being used.

In Scala 3 implicits have been completely redesigned and now the provsision of contextual environmental data is defined using a given keyword, their use in creating type class instances is indicated via the using keyword, extension methods are reprtesente dusing the extension keyword and implicit conversions are defined as an instance of Conversion[A, B] which makes them harder to misuse.

Providing Contextual Environments

For example, in Scala 2 we might have written:

import scala.concurrent._

implicit val ec: scala.concurrent.ExecutionContext =
    ExecutionContext.global

def calculate(i: Int)(implicit val execContext: ExecutionContext): Future[Int] =
    Future(i * i)

This defines an implicit val called ec which can e used as a value for any parameter list marked with the keyword implicit where a value is not provided by the caller of that method. Thus, if the programmer calls calculate above and does not provide an execution context then the value in ec will be used instead.

In Scala 3 we have the keywords given and using. Given is used to indicate a value that ca be used with a using parameter and using defines a parameter that can be provided with a given parameter, for example:

import scala.concurrent.ExecutionContext

given ExecutionContext =
    ExecutionContext.global

import scala.concurrent.Future
def calculate(i: Int)(using exeContext: ExecutionContext): Future[Int] = {
    Future(i * i)
}

3. Better Code Layout

Many programming languages are what might be called curly bracket languages. That is the beginning and ended of a block of code is represented by a set of curly brackets. Examples of curly bracket languages include Java, JavaScript, C, C++, Scala 2 and C#. However, Python takes a different approach and relies on indentation and layout to define how code is associated. This can avoid the situation where 3, 4 or more lines are required to close a set of curly brackets. 

For example, in Scala 2 (or Scala 3 using the curly bracket syntax), we can write a class definition as:

class X(name: String, age: Int) extends A {
   def printMe(): Unit = {
       println(name, age
   }
}

Using the indentation style in Scala 3 we can write this as:

class X(name: String, age: Int) extends A:
    def printMe() = println(name, age

Interestingly Sclaa 3 offers the optional end marker. This is useful when a method of type becomes quiet long and you wish to indicate that the reader has reached the end of the definition, for example:

def longDefintion(...) =
    ...
    if ... then ...
    else
        ... // a large block
    end if
    ... // more code
end longDefintion

The if statement has also been revised (along with several toher control flow statements) to make them easier to read. For example in Scala 3 we can now write:

def compare(a: Int, b: Int): Int =
  if a < b then
      a
  else if a == b then
      0
  else
      b

4. Better Support for Cross Platform development

The Scala 3 compiler generates TASTy files, rather than JVM byte code files. TASTY is an acronym derived from the term Typed Abstract Syntax Trees. It is a higher-level format than the JBVMs byte codes. This is usful as they contain more information than a JVM file and can be used to generate runtime executable files for a variety of different platforms from JVM byte code class files to JavaScript files or even nature executables. Thus, the same output from the compiler can be ‘executed’ on different runtime environments. The key points to note with this are:

  1. At compile time the scalac compiler generates TASTy files.
  2. At runtime the TASTy file is converted into a set of byte codes (or whatever is required) dynamically without the developer having to perform another step.

TASTy files also allow the compiler and other tools to provide more analytics and feedback to the developer.

5. Migration Tools

Why Should I upgrade to Scala 3?

Although Scala 2 and Scala 3 are not source code compatible there are tools available to help with the migration of Scala 2 source code to Scala 3. These tools are pretty well established now and in general do a very good job of translating the code. There are of course usually a few edge cases which require human intervention, but in the main the job can be accomplished relatively painlessly.

There are some caveats on this including:

  • The project must not depend on a macro library that has not yet been ported to Scala 3.
  • The project cannot us a compiler plugin that has no equivalent in Scala 3.
  • The project must not depend on scala-reflect.

See the guide to Scala 3 migration for more details.

Summary

There are few reasons not to migrate to Scala 3 and many benefits such as simpler syntax and improved semantics for Implicits to be obtained. Now really is the time to switch to Scala 3. Given that much of the syntax is the same all that is needed for most Scala programmers is a short upgrade course on the new features!


Would you like to know more?

If you found this article interesting you might be interested in our Scala 3 Training Course

Share this post on:

We would love to hear from you

Get in touch

or call us on 020 3137 3920

Get in touch