The exciting new version of Scala 3 brings many improvements and new features. Here’s a short digest for all those who are still unfamiliar with the changes and improvements that are coming in Dotty / Scala 3. Besides greatly improved type inference, the Scala 3 type system also offers many new features, giving you powerful tools to statically express invariants in the types
Two new features in Scala 3’s type system are intersection and union types. They embrace set theory more explicitly to improve the soundness of the type system, where a type can be considered a set and the instances of it are the members of the set.
A union type A | B
represents a type that has values of type A
or B
at a given time. A pattern match must be performed at the point of usage to extract the exact type.
val a: A | B = new A()
val b: A | B = new B()
They are commutative, so A | B
is the same as B | A
.
An intersection type A & B
represents a type that has values of type A
and B
at the same time.
trait TwitterPost
trait InstagramPost
def shareOnTwitter(twitter: TwitterPost)
def shareOnInstagram(instagram: InstagramPost)
val both: TwitterPost & InstagramPost = new TwitterPost with InstagramPost
shareOnTwitter(both) // A & B <: A
shareOnInstagram(both) // A & B <: B
A & B
is same as B & A
A
and B
, A & B
is a sub type for both A
and B
C
then C[A & B] <: C[A] & C[B]
Scala 3 adds support for "enums", which are to sealed traits like case classes were to classes. That is, enums cut down on the boilerplate required to use the "sealed trait" pattern for modeling so-called sum types, in a fashion very similar to how case classes cut down on the boilerplate required to use classes to model so-called product types.
enum Direction:
case North extends Direction
case South extends Direction
case East extends Direction
case West extends Direction
Similarly, we can add instance data to the enumerated instances:
enum Weekday(val index: Int):
case Mon extends Weekday(0)
case Tue extends Weekday(1)
case Wed extends Weekday(2)
case Thu extends Weekday(3)
case Fri extends Weekday(4)
case Sat extends Weekday(5)
case Sun extends Weekday(6)
As you know, in scala 2 traits cannot have parameters; we have abstract classes for that. However, abstract classes cannot be mixed into different parts of the class hierarchy. Well, now the traits are getting parameters too, which means syntax like this:
trait Foo(val s: String) {
...
}
Of course, as traits can take parameters now and as we know that multiple traits can be mixed in hierarchy, this feature could have resulted in ambiguities. To prevent this, Scala has laid the following rules and anything other than that is treated as illegal
C
extends a parameterized trait T
, and its superclass does not, C
must pass arguments to T
.C
extends a parameterized trait T
, and its superclass does as well, C
must not pass arguments to T
.Scala 3 introduces full language support for type lambdas without having to resort to ugly boilerplate or external libraries.
Let’s take a popular use case where F[_]
is needed, but we want to pass a type constructor that takes two types (such as Map or Either), or in other words, we want to pass (﹡ → ﹡) → ﹡
where ﹡ → ﹡
is needed.
Instead of having to define a type lambda like this:
({ type T[A] = Map[Int, A] }) #T
We will be able to simply say
[A] => Map[Int, A]
Here are some additional features and improvements that were included in scala 3
Like with dependent function types, Scala 2 supported methods that allow type parameters, but did not allow programmers to abstract over those methods. In Scala 3, polymorphic function types like [A] => List[A] => List[A] can abstract over functions that take type arguments in addition to their value arguments.
Compiler will now match the types when comparing values and fail the compilation if there’s a mismatch, e.g. “foo” == 123. We can already model this via type classes (it’s actually done in several libraries), but with Scala 3 we’re getting the native language support.
Scala 3 introduces direct support for typeclasses using contextual features of the language. Typeclasses provide a way to abstract over similar data types, without having to change the inheritance hierarchy of those data types, providing the power of "mixin" interfaces, but with additional flexibility that plays well with third-party data types.
Hide implementation details behind opaque type aliases without paying for it in performance! Opaque types supersede value classes and allow you to set up an abstraction barrier without causing additional boxing overhead.
This is based on the union types feature, which allows us to define the result type of scary operations that can throw a null pointer exception (useful for interoperability with Java) as Foo | null.
Scala 3 introduces creator applications, which is a concise way to create instances of a class even if it does not have an apply
method in its companion object.
These will basically be a replacement for implicit classes, e.g. extension StringOps for String { … }, but I didn’t dig too deep into this yet. Take a look at this PR if you want to find out more.
Scala 3 makes braces optional (!). This new Python-esque syntax helps ensure good indentation, as well as reducing the verbosity and visual noise of Scala code
Scala 3 adds new syntax for control operators, allowing you to omit parentheses that would have been required under Scala 2.x syntax. This makes the language more uniform (as parentheses were not required for guards in conditionals, for example), and modernizes the syntax of the language.
I am a Software Developer, Open Source enthusiast and Freelancer. I am passionate about Scala, Microservices & Full Stack Web Development. I'm open for Freelance and Student mentoring.
Phone: +237 699-131-434Email: [email protected]