Scala 3: Infix Operator Notation
For a long time, Scala has supported a useful “trick” called infix operator notation. If a method takes a single argument, you can call it without the period after the instance and without parentheses around the argument. This post describes changes in Scala 3.
For example, if I have a Matrix
class and a +
method to add matrices, element by element, I can write matrix1.+(matrix2)
or use the less cluttered and more intuitive syntax, matrix1 + matrix2
. The intention was to support true operators, like +
in intuitive contexts like this. However, there was no restriction on what methods applied in Scala 2, so if I also had a plus
method, then matrix1 plus matrix2
was allowed.
Used judiciously, this is fine, but dropping the punctuation occasionally makes the expressions ambiguous for the compiler or worse, for the reader. Hence Scala 3 is tightening the rules a bit to encourage more limited use.
Let’s revisit an example I discussed in Scala 3: Contextual Abstractions, Part II:
In Scala 3, the <+>
method can be used as is as an infix operator, because its name uses just operator characters instead of alphanumeric and _
characters. However, combine
can’t be used as an operator in the old way, unless I use back ticks or wrap the the argument in {}
:
The {}
option supports common usage where a function literal is passed to a method. Also, for backwards compatibility, all the standard collection operations are allowed to be used in infix notation without back ticks or {}
.
If I really want combine
to be used with infix notation, I declare it infix
:
I’ll explain @targetName
in a moment. The new infix
keyword tells the compiler to allow infix notation for combine
in user code without restriction. Note that it is necessary to use infix
in NumericMonoid
's definition, too, even though the abstract declaration in Semigroup
is declared infix
.
The @targetName
annotation is a new feature for specifying the alphanumeric name the compiler should use for the generated byte code for the <+>
method. If I wanted to call this method from Java code, I would use plus
. This name is not visible in Scala code. Also, I can’t use "combine"
as the argument for the annotation, as that name would collide with the actual combine
method name.
When I compile these definitions, then use javap
to analyze the class file, I see that <+>
has the full byte code signature public default T plus(T, T);
. The default
keyword comes from the fact that the Semigroup
trait is implemented in byte code as a Java interface and the implementation of <+>
is a default method implementation for <+>
.
If I remove the @targetName
annotation, recompile, and then run javap
, then <+>
has the signature, public default T $less$plus$greater(T, T);
, using Scala’s default encoding for non-alphanumeric characters.
As for many breaking changes, these restrictions on infix notation will start with Scala 3.1 or when the -source:future
compilation flag is used with Scala 3.0.
What about postfix notation?
For methods that take no arguments and they are not defined with empty parentheses, ()
, it’s been a common trick for DSLs to use postfix notation. For example, suppose I have an extension method to convert Double
literals to some Dollars
type in a financial application, 100.0 dollars
.
Postfix expressions are even more ambiguous than infix expressions. They are not technically deprecated, but for a while it’s been necessary to enable the language feature for them, such as import scala.language.postfixOps
. That remains true in Scala 3.0, but I’ve been told that a subsequent release of Scala 3 may finally, officially deprecate postfix expressions and remove them in a later release.
You can start reading the rough draft of Programming Scala, Third Edition on the O’Reilly Learning Platform. Most of the chapters are now available and the final release of the book will be coming soon!