Categories
Tech

Scala Saturday – Infix Notation

I have been using infix notation already in previous Scala Saturday posts, but I decided that it deserves some comment in its own right.

Perhaps you need to do some math with complex numbers, so you whip yourself up a quick Complex case class with a plus method to perform addition:

case class Complex(re: Double, im: Double) {
  def plus(other: Complex): Complex =
    Complex(re + other.re, im + other.im)
}

No sweat. Now you can add a couple of complex numbers:

val a = Complex(2, 5)
val b = Complex(1, -3)
val c = a.plus(b)
// c: Complex = Complex(3.0,2.0)

Wouldn’t it be great, though, if we could just write this?

val c = a plus b

The Fix Is In

If this were Java, we’d be AOL. But good news! Scala allows for infix notation. Martin Odersky in Scala by Example puts it this way:

The binary operation E op E′ is always interpreted as the method call E.op(E′).

In other words, you can write this:

val x = obj.method(arg)

… like this:

val x = obj method arg

So, in fact, the following works like a charm:

val a = Complex(2, 5)
val b = Complex(1, -3)
val c = a plus b
// c: Complex = Complex(3.0,2.0)

That’s why you can create a Range this way:

val r = 1 to 7
// r: scala.collection.immutable.Range.Inclusive = Range(1, 2, 3, 4, 5, 6, 7)

The Scala library has defined an extension method to for Ints that constructs a Range from a beginning Int to an ending Int.

Smooth Operators

This option of writing method calls in the form of binary operations allows you, as you’ve seen already, to ditch some clutter, but it also lets you write in more expressive mathematical terms when appropriate. We add real numbers and concatenate strings with a + sign. Why shouldn’t we do the same with complex numbers?

Now technically speaking, Scala does not provide operator overloading. It does something arguably better: It lets you name a method almost anything you want—a number of mathematical operators included! So then, let’s define a + method:

case class Complex(re: Double, im: Double) {
  def plus(other: Complex): Complex =
    Complex(re + other.re, im + other.im)
  def +(other: Complex) = plus(other)
}

Now you could write this:

val a = Complex(2, 5)
val b = Complex(1, -3)
val c = a.+(b)
// c: Complex = Complex(3.0,2.0)

But (in my best Graham Chapman) that’s just silly! The infix option allows you to write an addition operation naturally as you’ve been doing since grade school:

val c = a + b
// c: Complex = Complex(3.0,2.0)

And you can even chain operations:

val x = Complex(1,1) + Complex(1,2) + 
  Complex(2,3) + Complex(3,5)
// x: Complex = Complex(7.0,11.0)

Without infix notation, you’d be stuck with this:

val x = Complex(1,1).
          plus(Complex(1,2)).
          plus(Complex(2,3)).
          plus(Complex(3,5))
// x: Complex = Complex(7.0,11.0)

… which is not terrible, but isn’t the infix notation a marked improvement?

Again, there’s nothing special about the + sign or any other mathematical symbol: It’s just a method. You can chain other methods calls together in an infix fashion, too. Looking at Ranges one more time, you can create a Range with a custom step size by chaining the to method with the by method:

val threes = 0 to 20 by 3
// threes: scala.collection.immutable.Range = Range(0, 3, 6, 9, 12, 15, 18)

Parenthetically Speaking

Infix notation also works when a method takes more than one input, but you do have to group the arguments with parentheses:

// Oops!
val s = "abc" substring 2,3
// error: ';' expected but integer literal found.
//       "abc" substring 2,3
//                        ^

// Ah, this is better
val s = "abc" substring (2,3)
// s: String = c

Also if the method takes multiple parameter sets, you can use infix notation for the first argument, but you must wrap that infix expression in parentheses in order to apply the second argument. Take foldLeft, for example:

val f = (1 to 7 foldLeft 5)(_*_)
// f: Int = 25200

That’s not bad, but I suppose it’s a matter of preference whether it is any better than the postfix version:

val f = (1 to 7).foldLeft(5)(_*_)
// f: Int = 25200

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.