Scala 3: The _, They Are a’Changing
(With apologies to Bob Dylan.)
This post covers how some uses in Scala 2 of the underscore, _
, are being replaced with alternatives. The contexts include import and the new export statements and type parameter wild cards. Along the way, I’ll discuss other changes for import statements, the new export statement, and a syntax change for repeated parameter lists (a.k.a., variable argument lists).
Scala 3.0.0-RC1 is out! This means the language changes should be done. The discussion of import syntax changes below was the last major change. For a concise summary of most of the notable changes in Scala 3, see my Scala 3 Highlights.
You can start reading the rough draft of Programming Scala, Third Edition on the O’Reilly Learning Platform. Most of the chapters are available. Feedback is welcome!
In Scala 2, the underscore is used for placeholders in parameterized types, function arguments, and match expressions. These usages remain in Scala 3. Here are examples for type and function parameters and match expressions:
Here is an example of a for comprehension that does validation of fields in a case class (say for example from user input in a web form):
While the underscore is still used for placeholders, Scala 3 uses new characters for wild cards in type parameters and import statements. Here are some examples of type parameter wild cards:
Note the three uses of ?
for type wild cards. I discussed given imports in Scala 3: Contextual Abstractions, Part III.
New Import Syntax
The RC1 release introduced changes to the import syntax, one of which replaces _
with *
as the import wild card:
Note that one use for _
is retained: hiding items. For aliasing an import, the old “arrow”, =>
, is replaced with a new keyword as
(which may be used for additional purposes in a future Scala release). This means that =>
now has one use, in function literals. As a convenience, it’s no longer necessary to use curly braces when aliasing a single imported item.
Using *
as the wild card is consistent with many other languages, including Java. The original reason for using _
instead of *
in Scala was to allow users to define members named *
(e.g., for multiplication) and to import them with import foo.*
. The _
was already reserved for other purposes, so why not?
In practice, very few people actually need to write import foo.*
. In addition, since the original decision, Scala added the back tick “escape” mechanism. So, in Scala 3, you can write import foo.`*`
. Incidentally, if you’ve never used back ticks for purposes like these, here are other examples of where they are handy:
Like for other breaking changes, the old Scala 2 uses of _
are still allowed in Scala 3.0. A subsequent release of Scala 3 will drop support for them.
New Export Feature
Scala 3 introduces an export
statement that makes it easier to add members to a type that are implemented by some internal object. Consider an example Service
that has an embedded authentication instance, but we want to make that instance’s methods part of the Service
interface:
Note that last line. We export the members of auth
that we want, using the same syntax as import statements.
In Scala 2, you would have to write methods in Service
that forward to the corresponding auth
methods. Export statements remove this boilerplate.
Repeated Parameters Syntax
Finally, let’s see another place where _
is disappearing; the syntax for repeated parameters:
This change makes the syntax more consistent. Notice how count
is defined, which is unchanged from Scala 2. The T*
indicates that zero or more values can be provided. Scala holds them in a collection, which is why I can call ts.size
.
Notice what happens in line 3. Does Scala automatically convert the Seq[T]
into a T*
? No, in both Scala 2 and 3. This is how it should work, because the compiler can’t know if you intend to convert the sequence to repeated parameters or pass the sequence as a single parameter. Hence, the sequence is treated as a single T
value in line 3, so 1
is returned. Line 4 does the correct thing; using the same trailing *
to tell the compiler to do the conversion. The trailing *
is the same syntax as for the declared repeated parameter list, for consistency.
Similarly in the case
clause, tail*
means zero or more elements of the sequence that are captured in a new collection tail
. For _*
, we use the placeholder _
, to ignore them. In Scala 2, we would use tail: _*
and _: _*
, respectively (the space can be omitted).
Final Thoughts and What’s Next?
The many uses of _
in Scala 2 was a disadvantage for beginners, because it was hard to read code and always know what the _
meant, until it became natural based on the context. However, the advantage of _
was not having to remember which character to use for which situation. However, it was decided that the disadvantages outweigh the advantages, so … changes.
In the next post, I’ll discuss type lambdas, and throw in dependent function types, and polymorphic function types for good measure.
You can start reading the rough draft of Programming Scala, Third Edition on the O’Reilly Learning Platform. Currently, the first six chapters are available, including the two (five and six) that cover contextual abstractions.