Dropping baggage in Scala 3

Dropping baggage in Scala 3

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 Chars 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.

Ensure the success of your project

47 Degrees can work with you to help manage the risks of technology evolution, develop a team of top-tier engaged developers, improve productivity, lower maintenance cost, increase hardware utilization, and improve product quality; all while using the best technologies.