Scala 2 is a mature and trustworthy language. Its market share includes diverse domains, from small web applications to extensive financial systems. However, Scala 2, like all tools, can be improved. The team at EPFL, led by Martin Odersky, spent years designing and implementing its successor, keeping all of Scala 2’s strengths but refining the predecessor. In early 2021 Scala 3 had its premiere – the result of their effort.
A year and a half later, many product managers still wonder if their teams should adopt Scala 3 or keep on using Scala 2. This blog post intends to give you an overview of the benefits and risks of choosing Scala 3.
Follow us, and don’t miss posts with detailed explorations of all the gains and dangers of migration coming in the following weeks.
The benefits
Let’s start with analyzing what added value Scala 3 brings to your projects compared to Scala 2.
Compatibility guarantees
Scala 2 is infamous for its incompatibilities between different versions of the language. The library compiled with Scala 2.11 can’t be used as a dependency of a project using the 2.12 version of the compiler and vice versa. Gaining access to most usability improvements of newer versions, such as 2.12.x to 2.13.x requires checking not only if all the dependencies are available but also non-trivial migration work.
In Scala 3, this is no longer the case. Thanks to the new comprehensive metadata format (TASTy) encoded in compiler output, it is possible to use a library compiled with any previous compiler version as a dependency. There is nothing unusual in depending on a library compiled with 3.0 by the project using Scala 3.2. This allows updating the compiler version without waiting for dependencies to catch up. The developers can then enjoy usability improvements of a new version right from the get-go.
Users no longer need to worry about the migration work when switching between minor versions of the compiler. The Scala 3 team ensures source compatibility between language versions. A dedicated system regularly tests the compiler’s nightly builds on hundreds of open-source real-life Scala projects and reports every incompatibility. Every once in a while an incompatibility delayed the release of the new version. The compiler team must be sure that the problem had been eliminated and the update would not break anyone’s code.
There is also something for projects with a more conservative approach to upgrade policies. The compiler team is dedicated to releasing the Long Term Support compiler versions. These versions provide the most stable environment for Scala developers guaranteeing a support term of at least three years. All new development that does not break compatibility guarantees, such as improvements in compilation speed or better linting options, will be backported to the current LTS.
Perfect for modeling your business domain
Scala is the best language for modeling complex business domains in a safe and efficient manner. Scala 3 comes with multiple improvements, like union and intersection types and simplifications, like enums or constructors in traits, making data and interaction modeling even more approachable and straightforward. Keep in mind, all is based on years of experience using Scala 2.
Scala’s compiler and type system are known for shielding users from unexpected errors at runtime. Thanks to the improvements mentioned earlier, developers easily benefit from this additional safety net of the compiler. Even the more powerful compile-time checks are readable and easy to express in Scala 3. Prior advanced knowledge from the developer becomes inessential.
Safety in the world of dynamic data
While modern statically typed languages can save us from many errors, we need to keep in mind that most of the data we interact with has a dynamic structure. In most languages, it means the compiler stops protecting us from our mistakes; but not in Scala 3.
In Scala 3, there are language mechanisms, such as type refinements and the Selectable trait, that allow us to write code that treats dynamic data as well-structured and statically typed. Moreover, proper library support allows us to have safe interactions with external systems. As a bonus, developers get nice quality-of-life improvements, such as working code completions in IDE for dynamic types.
Prototypes of said libraries already exist. Iskra, the safe api for Spark data frames, is one of the examples.
Easier to understand and more powerful abstractions
The main design goal of Scala 3 is to simplify overloaded and complicated features from Scala 2. As a result Scala 3 features also became more composable and keeping Scala 2’s strength.
The best examples are changes in implicits that are the most blessed and cursed feature in Scala 2. Most of the problems come with the fact that `implicit` was overloaded with meanings. In Scala 3, implicits are replaced by dedicated, meaningful keywords, such as extension, given, using, each matching the usage context. Moreover, Scala 3 offers new ways, like context functions, for library authors to hide the fact that implicits are even used. Those changes meant making Scala more readable and approachable for newcomers. This, in turn, will improve the longevity and maintainability of Scala projects.
Simple and gradual metaprogramming for everybody
As projects grow, the need to add a bit of compile-time operations may rise. Scala 2 offers such a tool called the macro system. It was experimental, but has proven to be so useful that it became the foundation of many core libraries. However, macros were infamous for their instability and being hard to use. In most cases, developers only needed quite trivial compile-time operations. A complex macro system was the wrong tool to use. .
Scala 3 made macro safer and more stable. Additionally and most importantly, it introduced straightforward tools for straightforward jobs. For instance, inline methods in Scala 3 look and behave exactly as any other method. Still, they are guaranteed to be executed during the compilation. This means that developers can access a feature to improve type safety or reduce the boilerplate with a minimal learning curve.
Attractive for developers
The job market for Scala developers is competitive. Scala developers are known for their love for experimentation and learning. Using all the new features and improvements that come with Scala 3, can be an important perk for many talents. This gives your company an edge to hire the best talents. Bear in mind, Scala 3 is not a new language but rather an evolution of Scala 2. This means your existing developers will adopt it overnight.
Young talents, who want to get involved with Scala, will use new training materials, which are focused solely on Scala 3. Scala 2 slowly but surely becomes legacy technology.
Quality of life improvements
The developer’s quality of life is essential to any company. Keeping this in mind, Scala 3 is set out to improve it.
New features like top-level functions, extension functions, context functions or enums make writing code an easier and more enjoyable experience. Others, like export clauses or typeclass derivation, allow to avoid boilerplate code that could frustrate developers. Scala 3 has native support for widely known tools (Metals, incremental compiler) making integration more stable in the long run. Moreover, the new language version is script-friendly and comes with an improved REPL and documentation engine.
The risks and how we can mitigate them
Deciding on Scala 3 requires knowing related risks and how significant their impact may be.
Let’s dive right in.
Tooling support
Developer’s satisfaction and productivity are usually directly proportional to the quality of the tooling used. Let’s examine how Scala 3 looks in comparison with Scala 2.
First, let’s take a look at Metals – the Scala Language Server. It allows using IDE features for Scala in Visual Studio Code, Eclipse, Neovim, and other editors. Both versions of the language have very solid support there. For Scala 3 however, Metals are ahead of the curve, supporting some features still considered experimental in the language.
Users of IntelliJ can use an official Scala plugin developed by JetBrains. It supports Scala 3 projects and as of November 2022, the support quality is decent and improving rapidly. Still, it is not yet on par with the support of Scala 2.
Scalafmt – the formatter for the language – works flawlessly with the newest version. Scala 3 is a first-class citizen in all mainstream build tools.
The missing link in a Scala 3 toolchain is a comprehensive linter. A solution is underway and should be published in 2023. For now, the compiler can provide some lints.
Compilation performance
Compilation time is a considerable cost factor. You may fear additional compile-time safety and quality-of-life improvements affect the compilation times negatively.
Right now, the benchmarks show that the average compilation time is as good as in Scala 2.13. For some projects, 2.13 performs slightly better; for others, Scala 3 has the advantage.
Nevertheless, Scala 3 has much better compilation times than Scala 2.12, which is still the most popular version used in commercial projects. The advantage over older compilers is even more significant.
Additionally, Scala 3 is the only actively developed version of the language and will see performance improvements for the compiler. Scala 2.13 compiler’s performance, however, will most likely not improve much.
Libraries
Sometimes Scala 3 is compared to Python 3. Python 3 was a language that outperformed its predecessor. For a long time it stayed widely untouched before finally rising to fame. The main reason for this lack of initial success was that most of the libraries were only published for Python 2, leaving Python 3 standing alone. What’s more, some problems manifested only in runtime, making them hard to spot and fix before actual execution. Scala 3 migration is definitely far away from the severity of Python’s problem. Scala is a statically typed language that moves the faults from runtime to compile time and by extension is much easier to find and solve them early.
Also, Scala 3 uses libraries built for Scala 2.13, as long as they’re not exposing macros in their API. Usually, it’s not even needed, as a large majority of essential libraries are cross-publishing their artifacts to Scala 3. In other words, Scala 3 is their first-class target.
Significant ecosystems such as Typelevel’s stack (cats) or Zio have been published for Scala 3 for a long time. The only notable missing pieces are Akka-http and libraries that depend on it a Play Framework, for example. As a side note: Our team cooperates with Lightbend to get akka-http published for Scala 3 as soon as possible.
The migration effort
Last but not least, we must mention the effort of the migration process. Conveniently, we can migrate a more extensive system one module at a time, as Scala 3 code can depend on Scala 2.13 ones and vice versa. We can also adapt a strategy of only creating new parts of the system using Scala 3, leaving older ones with Scala 2.13. There are, however, some caveats. The most important of them is that any macro that is used across different modules needs to be reimplemented in Scala 3.
The cost of adapting Scala 3 may vary depending on the exact structure of the project. We at VirtusLab are offering our expertise in that matter free of charge. We can provide developers that can assess migration costs as well as help to conduct a successful migration process.
Should I migrate to Scala 3 now?
As we can see, Scala 3 offers many benefits over Scala 2. However, it also brings some risks. Still, the benefits outweigh them. Nevertheless, the right approach to migration is key to a successful and smooth migration experience.