Bartek Andrzejczak

Musings on software development

Ternary Operator in Scala

Last Saturday I was a part of Scalar conference in Warsaw. Softwaremill, the main organizer of the event, prepared a really cool contest - everybody at the conference got a piece of paper with a code printed on it. There were three different pieces of code, so you had to find two other people with different pieces (socialization, yay!). When you did it, they gave you the fourth, remaining piece of code. The task was to find a mistake in a code. Here is the original code:

1
2
3
4
5
6
7
8
9
10
11
12
object ScalarPuzzle extends App {

  implicit class TernaryOps(cond: => Boolean) {
    def ??(thenBlock: => Unit) = new {
      def ::(elseBlock: => Unit): Unit =
        if(cond) thenBlock else elseBlock
    }
  }

  (1 + 1 == 2) ?? println("True") :: println("False")

}

It uses implicit conversions, by-name parameters and infix operators to enrichen Scala with ternary operator. The only difference between Java and the code above is, that in Java you would write condition ? thenBlock : elseBlock, while here you would have to write condition ?? thenBlock :: elseBlock. The reason for the change is that you cannot use single : as a method name in Scala, because it’s already used for explicit typing.

Can you already spot the error? IntelliJ told me, that it cannot resolve symbol ::. Weird… It’s right there. Together with my friends we’ve thought for a while, that there’s something wrong with the return type of ?? method, but it was a big miss. I’ll give you a hint:

1
val list = 1 :: 2 :: 3 :: 4 :: Nil

It’s a working example of list construction. Does Integer class have :: method? Nope. Maybe there’s an implicit conversion in action? Not at all. :: method is actually a part of List class and Nil extends List[Nothing]. How is that possible? Operator associativity.

All the operators like myOp, +, ! are executed as a methods of object on their left handside. On the other hand ::(and any other method that name ends in :) is executed as a method of object on its right handside. It means that one + two can be written as one.+(two), but one :: two is equal to two.::(one).

How can you fix it? Just change the name of the :: method. Name it :> or :| or whatever else. Pretty easy, huh? But what if you’d really wanted to use it like cond ?? then :: else? There’s an interesting solution to that problem, and it contains even more implicit conversions, and even more by-name parameters. Interested? Read on.

Ternary operator done right

First, let’s write the desired operator syntax, with regular method calls instead of infix operators. It should look something like this:

1
(println("False")).::((1 + 1 == 2).??(println("True")))

We’ll need at least two classes with implicit conversions into them:

  • One for the else clause, with :: method
  • One for condition and then clause, with ?? method

Let’s start with the second one. It’s kinda tuple, because these are just two arguments with different types, but tuple._2 just doesn’t look meaningful in the code. We could implicitly convert (1 + 1 == 2) into some wrapper class and then set thenClause field of this class with ?? method, but I hate creating classes in invalid states. That way you could call (1 + 1 == 2) :: println("False") which is obviously wrong. What I’m going to do is to create an implicit class with ?? method, which call will result in a container object, containing both condition and then clause. No invalid state, meaningful names. Let’s do this. My first guess was to store condition and then clause inside case class, like this:

1
case class TernaryIfThenClause(c: => Boolean, tc: => Unit)

It looks awesome, but it doesn’t compile. Why? By-name parameters cannot be vals. That’s it. Obtain them, use them, but don’t expose them. Sure. Makes sense. I’ve by-passed this impediment with regular class and lazy vals, like this:

1
2
3
4
class TernaryIfThenClause(c: => Boolean, tc: => Unit){
  lazy val cond = c
  lazy val thenClause = tc
}

Why lazy vals? It’s quite simple. By-name parameters delay evaluation of the parameters. When you assign them to vals, they will be evaluated as soon as the class is instantiated. That’s not what we want. We want them to be evaluated when they’re first accessed. That’s why they are lazy vals. You could transform them into methods easily, changing lazy val into def, but I think this solution is more elegant.

Now let’s convert our cond ?? thenClause expression into this class:

1
2
3
implicit class TernaryAssoc(cond: => Boolean){
  def ??(thenClause: => Unit) = new TernaryIfThenClause(cond, thenClause)
}

Fairly easy, right? Great. All that’s left is to create a method on another implicit class taking => Unit as a parameter, which will take TernaryIfThenClause as an argument and return an expression value. It’s very similar to TernaryAssoc class.

1
2
3
4
implicit class TernaryElseClause(elseClause: => Unit){
  def ::(ifThenClause: TernaryIfThenClause) =
    if(ifThenClause.cond) ifThenClause.thenClause else elseClause
}

That’s it folks. We’ve defined fully working ternary operator for Scala. Or did we? We could execute the example code from the bottom of the Scalar puzzle, but we’re still missing something. Result value! Right! To achieve that, we need to replace all Unit types with parametrized types. Here is the complete code with types:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
object ScalarTernaryTyped extends App{

  class TernaryIfThenClause[X](c: => Boolean, tc: => X){
    lazy val cond = c
    lazy val thenClause = tc
  }

  implicit class TernaryAssoc(cond: => Boolean){
    def ??[X](thenClause: => X) = new TernaryIfThenClause(cond, thenClause)
  }

  implicit class TernaryElseClause[X](elseClause: => X){
    def ::(ifThenClause: TernaryIfThenClause[X]) =
      if(ifThenClause.cond) ifThenClause.thenClause else elseClause
  }

  println((1 + 1 == 2) ?? "True" :: "False")
  println((1 + 5 == 2) ?? "True" :: "False")

}

Disclaimer

If you want to put that in your code, import it into your classes and use it, just like in Java, I have one advice for you. Don’t. Seriously. Don’t do it. And even if you want to do it, don’t add a link to my post as a comment! I don’t want to be responsible for it. This code introduces two implicit conversion, to do one simple task. It just puts more garbage on your potential stack trace. Why bother if you have perfectly good if else expression?

You know why Java has and Scala hasn’t ternary operator? Result value! In Java, if your write

1
String test = if(1 == 1) "a" else "b";

it won’t compile. It’s just a statement, not an expression. That why you need ternary operator. Instead of:

1
2
3
4
5
String test;
if(1 == 1)
  test = "a";
else
  test = "b";

you can write:

1
String test = 1 == 1 ? "a" : "b";

On the other hand, in Scala if is an expression which yields value. Why do you need a ternary operator if you can just write:

1
val test = if (1==1) "a" else "b"

It’s not like ternary operator saves a lot of typing, and it’s less readable than plain old if else, right?

Comments