Infix Methods in Scala 3

2 minute read

No doubt, Scala is an expressive language. This article will be short and to the point, regarding a particular expressive feature of Scala: infix methods.

Background

Scala is expressive and powerful in many ways, but two (quite simple) features make it distinctive from most other languages:

  • method naming including non-alphanumeric characters, which allow math-like operators such as ++, -->, ? and more
  • the ability to infix methods with a single argument

We’re going to stress on the second. The idea is that, if a method has a single argument, we can write it as if it were an operator. An example:

case class Person(name: String) {
  def likes(movie: String): String = s"$name likes $movie"
}

In this case, an instance of Person can call its likes method either in “normal”, Java-style dot notation, or it can call it in “infix operator”. In other words, if we instantiated a person we could call its method as usual:

val mary = Person("Mary")
val marysFavMovie = mary.likes("Forrest Gump")

but we can also call it in a different way:

// identical
val marysFavMovie2 = mary likes "Forrest Gump"

which the compiler rewrites as mary.likes("Forrest Gump"), but looks much closer to natural language. This works for any method with one argument and allows us to write object.method(arg) or object method arg.

Infix Methods and Scala 3, Now Explicit

Infix methods were introduced to allow the operator notation for methods that look like operators: +, -->, !, :: etc. However, Scala 2 also allows regular methods - see the likes example above to be invoked with the infix notation.

In Scala 3, infix notation for “regular” (i.e. alphanumeric) methods will be discouraged and deprecated (according to the docs starting in Scala 3.1). Expressions like mary likes "Forrest Gump" will now compile with a warning. However, Scala 3 allows us to remove the warning if the method comes annotated with @infix:

import scala.annotation.infix

case class Person(name: String) {
  @infix
  def likes(movie: String): String = s"$name likes $movie"
}

The @infix annotation is only required for alphanumeric method names. If we had

def ::(person: Person): String = "$name talks to ${person.name}"

we could call mary :: bob no problem.

The New Infix Definition

So far, nothing rocket science. However, the following came by surprise. Assume we had the class Person without any inside implementations:

case class Person(name: String)

Now, in some other part of the code, we can define a method that looks like this:

@infix
def (person: Person).likes(movie: String): String = s"${person.name} likes $movie"

which would then enable us to write, as before,

val mary = Person("Mary")
val marysFavMovie3 = mary likes "Forrest Gump"

Now that’s some funky syntax. The construct above is a method called likes which we can invoke only on an instance of Person. Not only that, but since it has one argument, we can also call it in infix position, and the infix rules - including annotations and deprecation - also apply here.

That funky method definition is an extension method. The syntax is shorthand for the longer-form

extension (person: Person)
  def likes(movie: String): String = s"${person.name} likes $movie"

which the docs aren’t too clear about at the moment.

More on extensions, in another article.

Conclusion

We’ve learned - or at least recapped - a few ideas about infix methods and how the rules change in Scala 3, plus a new friend with infix extension methods with shorthand syntax.

More to come on Scala 3!

Tags:

Updated: