Dropping baggage in Scala 3
by Noel Markham
- •
- May 20, 2021
- •
- scala• scala3• functional programming• functional
- |
- 6 minutes to read.

After a couple of heavy weeks looking at how typeclasses are set up in Scala 3, and how to automatically derive them, we’re going to take a look at some changes in Scala 3 to make reading and writing the code a little more pleasant.
Many of these updates here are removing or updating parts of the language that were infrequently used, so for the everyday usage of Scala, you may not see many differences at all.
do-while
The do-while
block was so rarely used that some developers did not even realize it existed.
Any do-while
block can be rewritten using a while
block, so it’s redundant.
Also, do
has a new meaning in Scala. In a few situations, the do
keyword in Scala can be used in place of parentheses.
XML Literals
When Scala started to gain traction in industry, having XML literals was a nice “selling point” to attract attention to the language.
Times have changed, XML isn’t as popular as it once was, and it doesn’t fully make sense for XML to be treated as a special case.
For now, XML literals are still available in the language, but there are plans for it to be removed completely.
So when writing new code, consider using the xml
string interpolator.
The compiler will complain if it is presented with invalid XML:
scala> import dotty.xml.interpolator.*
scala> val i = 42
val i: Int = 42
scala> xml"""<message>$i</message>"""
val res0: scala.xml.Elem = <message>42</message>
scala> xml"""<this is not valid xml"""
1 |xml"""<this is not valid xml"""
| ^
| '>' expected but 'i' found
| This location contains code that was inlined from rs$line$4:1
22
Ah, 22. We’ve all been caught on that error somewhere in Scala related to the number 22, often in places where they aren’t expected. Both tuples and functions can now have more than 22 elements, so should you find yourself needing more, you don’t have to work around your approach or dive into a library such as shapeless.
Procedure syntax
You used to be able to create functions returning Unit
by omitting the =
from a function definition.
While understandable in theory, accidentally missing the equals from your non-unit-returning function in practice could result
in more time debugging than you’d ever admit to.
The =
is mandatory now. The return type of the function can still be inferred, so you can either write
def unitFunction = { ... }
or def unitFunction: Unit = { ... }
.
I think it’s good to label the return types explicitly, as then you are asking the compiler to agree with your definition
rather than asking the compiler to work out what you meant.
[this]
Both private[this]
and protected[this]
are now deprecated. I have never been a fan of these modifiers:
if you default to writing pure, immutable code, then there should be no downsides, if any, for exposing the workings of a class.
Package objects
Package objects are no more. You can now add this information at the top level of any file for that package.
Assuming a file of any name containing the following definition:
package util
extension(i: Int)
def add(j: Int): Int = i + j
// ...
This file can go on to include other definitions such as classes or traits.
This is not a “special file” like package.scala
was considered.
You can use this as you would have used a package object:
scala> 5 add 5
1 |5 add 5
|^^^^^
|value add is not a member of Int
scala> import util._
scala> 5 add 5
val res0: Int = 10
Existential types
The forSome
keyword has been dropped. This allowed you to avoid propagating unnecessary type parameters through a function.
Wildcards can often be used where the forSome
would have been. A slightly contrived example:
scala> def allElementCount(x : Array[CharSequence]) = println(x.map(_.length).sum)
def allElementCount(x: Array[CharSequence]): Unit
This function that adds up all the Char
s in all the elements in the array will not work directly for Strings
:
scala> allElementCount(Array[String]("will", "this", "work"))
1 |allElementCount(Array[String]("will", "this", "work"))
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| Found: Array[String]
| Required: Array[CharSequence]
With Scala 2, one way around that would have been to define the function as:
def allElementCount(x : Array[T] forSome { type T <: CharSequence }) = println(x.map(_.length).sum)
This would allow us to skip a type parameter on the function,
but it would still work for any array of values containing a CharSequence
.
We can do this now with a wildcard:
scala> def allElementCount(x : Array[_ <: CharSequence]) = println(x.map(_.length).sum)
def allElementCount(x: Array[? <: CharSequence]): Unit
scala> allElementCount(Array[String]("will", "this", "work"))
12
This example was reworked from David R. MacIver’s blog post on Existential types in Scala.
Stay tuned to our blog and Twitter account over the next few weeks for more on new and improved features in Scala 3.