Scala match tuples helps cut down on unwieldy nested ifs

Alonso Del Arte
7 min readJun 5, 2019

--

Photo by Dean Brierley on Unsplash

Perhaps you have seen professionals and amateurs alike write code like this:

  if (myVar == constA) {
actionA();
} else if (myVar == constB) {
actionB();
} else if (myVar == constC) {
actionC();
} else if ...
// and so on and so forth
}

Reasonably you expect the professionals to know better and use a switch-case statement, like so:

  switch (myVar) {
case constA: actionA();
break;
case constB: actionB();
break;
case constC: actionC();
break;
case ... // and so on and so forth
break;
default: throw someError;
}

You might find it more plausible that professionals forget about the “fall-through” behavior of the switch-case statement and neglect to put in a break where it is needed.

Scala doesn’t have switch-case, but it has something that can look very similar, the match statement. For example:

  myVar match {
case constA => actionA
case constB => actionB
case constC => actionC
... // etc.
case _ => throw new Exception("Unexpected case")
}

No break command needed to prevent case constA from falling through to case constB. The default case is labeled case _ rather than default, but that’s a superficial difference, as is the use of => instead of :, or the lack of semicolons.

Though you can use semicolons if you want. Don’t use break though, that has another purpose in Scala.

Here is a more concrete example, using the infamous FizzBuzz. This example still doesn’t even begin to hint at the power of Scala match expressions:

def fizzBuzz(n: Int): Any = (n % 15) match {
case 0 => "FizzBuzz"
case 3 | 6 | 9 | 12 => "Fizz"
case 5 | 10 => "Buzz"
case _ => n
}
(1 to 100).map(fizzBuzz(_))

If you don’t feel like typing this into a file, compiling it and executing it, you can just run it in Scastie, the online Scala REPL.

This is already much cleaner and neater than how’d you do it in Java. And it looks more deliberate, too, than a Java switch-case statement, where code reviewers might wonder if you left out a break on purpose.

This probably won’t compile to tableswitch or lookupswitch in the Java Virtual Machine, but I sincerely doubt you will notice any performance penalty even if a performance penalty does exist.

It is also cleaner than in JavaScript, which is not really based on Java but does have switch-case statements pretty much like in Java. And in Scala, Any gives you the flexibility that you get in JavaScript without the nightmares of unexpected type inference.

But in Scala we can do FizzBuzz even better, by matching to a pair of moduli rather than a single modulus. In Scala, you can create a tuple any time you want with very little fuss.

def fizzBuzz(n: Int): Any = (n % 3, n % 5) match {
case (0, 0) => "FizzBuzz"
case (0, _) => "Fizz"
case (_, 0) => "Buzz"
case _ => n
}
(1 to 100).map(fizzBuzz(_))

I’m also providing a Scastie link for this one.

There are fifteen possibilities for the tuple (n % 3, n % 5), just as there are fifteen possibilities for the single n % 15. But with the tuple, we can express the possibilities more concisely: instead of, say, 3 or 6 or 9 or 12 but not 15, we want (0, m), where m may be 1, 2, 3 or 4 but not 0, and we don’t care which.

You do have to take care to put the narrower cases before the more general cases. If, for example, you put (0, m) before (0, 0), the function will fail to match 15, 30, 45, 60, … to “FizzBuzz” and will match those to just “Fizz”.

The elements of a tuple don’t all have to be the same type, and this is what can really help you cut down on potentially confusing nested if statements.

For example, if a flag is true, a number is one of two or three specific cases, and a String converted to lowercase is one of four specific cases, the program should take such and such action.

But if the number is one of a couple other specific cases, regardless of the String, the program should take a slightly different action.

And if the flag is false but the number is a particular value and the String in lowercase also meets a certain criterion, the program should also take the slightly different action. Any other combination of values should trigger one of two specific exceptions.

How would you program that in Java? You would probably write nested if statements, at least three levels deep. Or you could create some kind of hash code and then sort through the relevant cases in a switch-case statement.

In Scala, you could just create a triple and then work out the possible cases, taking care to place the narrowest cases before the more general cases.

I know this example sounds rather contrived and unlikely to occur in an actual programming situation (as opposed to a programming exercise). So I will now present a concrete example from a program I’ve been working on for months.

To understand this example, you don’t really need to know about domains of algebraic integers. Though if you don’t know but want to know, you can check out my Medium article on the topic.

Is 7 divisible by 3 + √2? In a domain that contains both of those numbers, yes, 7 divisible by 3 + √2. Is 7 divisible by √2? No, it’s not, because 7√2/2 is not an algebraic integer in any domain.

In my Java program, both numbers would have to be represented as instances of the RealQuadraticInteger class. My program would give you the answer that 7 divided by 3 + √2 is 3 − √2, with the understanding that 7 is a shorthand for 7 + 0√2.

But 7 could also be 7 + 0√3, 7 + 0√5, etc. It could also be something like 7 + 0√−19, represented by an instance of the ImaginaryQuadraticInteger class. But I would expect my program to give the right result without troubling you too much about how exactly 7 is represented.

I won’t quote the divides() function here, it would really swell up this article’s word count. Go to the GitHub source listing, scroll down to the “public QuadraticInteger divides(QuadraticInteger divisor) throws NotDivisibleException” line.

That’s line 893 in the current version. It goes on to line 996. Some of the length is due to my not yet taking full advantage of the Fraction class in that version, but some of the length is due to all the if statements, some nested four levels deep.

I will continue to develop the Java version of my program, but I will also work on it in Scala. I could dump Java source into Scala files in IntelliJ through cut and paste from a plaintext editor, and let IntelliJ translate it to Scala for me.

But I imagine that might not always produce the most idiomatic Scala. And also, through a more rigorous application of test-driven development, I intend to produce a version of the program that is more elegant and perhaps at times more efficient.

Now follows a quotation of what I’ve got so far for the equivalent to QuadraticInteger.divides() in my Scala program. It is not a full quotation, but it is close to full.

  @throws(classOf[NotDivisibleException])
def /(divisor: QuadInt): QuadInt =
if (this.ring == divisor.ring) {
if (divisor.norm == 0) {
throw new IllegalArgumentException("Division by zero")
}
val dividendRegFract = new Fraction(this.regPart,
this.denominator)
val dividendSurdFract = new Fraction(this.surdPart,
this.denominator)
val divisorRegFract = new Fraction(divisor.regPart,
divisor.denominator)
val divisorSurdFract = new Fraction(divisor.surdPart,
divisor.denominator)
val quotientRegFract = (dividendRegFract * divisorRegFract
- this.ring.radicand * dividendSurdFract
* divisorSurdFract)/divisor.norm
val quotientSurdFract = (dividendSurdFract * divisorRegFract
- dividendRegFract * divisorSurdFract)/divisor.norm
val notDivisibleFlag = (this.ring.hasHalfIntegers,
quotientRegFract.denominator,
quotientSurdFract.denominator) match {
case (_, 1, 1) => false
case (true, 2, 2) => false
case _ => true
}

if (notDivisibleFlag) {
throw new NotDivisibleException("Not divisible",
this, divisor, fractArray, this.ring)
}
QuadInt(quotientRegFract.numerator.toInt,
quotientSurdFract.numerator.toInt, this.ring,
quotientRegFract.denominator.toInt)
} else {
throw new AlgebraicDegreeOverflowException
}

As you can see, there are still nested ifs. But the match statement (in bold) vastly simplifies the task of checking a Boolean and comparing if two integers match each other to 1 or to 2, so there is no need to nest the if that checks the denominators in the if that checks the Boolean property ring.hasHalfIntegers (which comes from the QuadRing class).

This passes the pertinent JUnit tests (I have yet to feel a pressing need to use ScalaTest). On the local Scala REPL, I tried out the examples mentioned earlier of 7 divided by 3 + √2 or √2.

scala> val ring = new algebraics.quadratics.RealQuadRing(2)
ring: algebraics.quadratics.RealQuadRing = Z[sqrt(2)]
scala> val ramifier = new algebraics.quadratics.RealQuadInt(0, 1, ring)
ramifier: algebraics.quadratics.RealQuadInt = sqrt(2)
scala> var numberA = new algebraics.quadratics.RealQuadInt(7, 0, ring)
numberA: algebraics.quadratics.RealQuadInt = 7
scala> var numberB = new algebraics.quadratics.RealQuadInt(3, 1, ring)
numberB: algebraics.quadratics.RealQuadInt = 3 + sqrt(2)
scala> numberA / numberB
res5: algebraics.quadratics.QuadInt = 3 - sqrt(2)
scala> numberA / ramifier
algebraics.NotDivisibleException: 7 divided by sqrt(2) is 0 + 7/2 * sqrt(2), which is not an algebraic integer
at algebraics.quadratics.QuadInt.$div(QuadInt.scala:193)
... 28 elided
scala>

In the numberA / numberB example that produced res5, the match triple was (false, 1, 1), so it matched case (_, 1, 1).

By contrast, numberA / ramifier triggered NotDivisibleException because (false, 1, 2) matched neither case (_, 1, 1) nor case (true, 2, 2) (note that Fraction represents 0 as 0/1 internally, though 0/2 would be valid from a mathematical point of view).

Hence notDivisibleFlag was set to false and so $div() decided to throw NotDivisibleException. None of this is on GitHub yet, QuadInt.scala is still very incomplete and not passing all the tests. When I get around to it, it’ll be in the alg-int-calc-scala/algebraics/ folder.

Scala matches can also be used to match types, but that’s a topic that will have to wait to another day, if it hasn’t been covered by others here on Medium (though outside of Medium, Alvin Alexander probably has some very good coverage of it).

It is of course possible to overdo tuple matching to the point of ridiculousness. Functional programming can be used to clarify just as it can be used to obfuscate, and it’s up to the programmer to choose wisely.

And it remains important to test your program (preferably through a non-dogmatic application of test-driven development) to make sure it gives the right results.

Nonetheless, the ability to match tuples in Scala is definitely something you should keep in mind if you program in Scala.

--

--

Alonso Del Arte
Alonso Del Arte

Written by Alonso Del Arte

is a Java and Scala developer from Detroit, Michigan. AWS Cloud Practitioner Foundational certified

No responses yet