Kotlin: Coding a Ternary Operator
In Java, the ternary operator lets you write conditional expressions. It looks like this:
String message = isSuccess ? "Success" : "Failure"
But this doesn’t work or exist in Kotlin. That’s because unlike Java, Kotlin lets you directly use if/else within an expression.
val message = if (isSuccess) "Success" else "Failure"
To some, this is a great improvement over Java and there’s no need for Kotlin to have a ternary operator. I do appreciate the additional clarity of if/else myself. There’s a lengthy and entertaining discussion on the Kotlin forums about it.
But to others, they may miss the ternary operator and wish that Kotlin had one. And so can we try to use Kotlin to write our own ternary operator?
val message = isSuccess T "Success" F "Failure"
Pretty good! This is done using the magic of Kotlin’s infix keyword to write functions that can be used between two arguments. This makes them look more like an operator rather than a traditional function. There are limits to what characters are allowed in function names, ? and : are not included, so I opted for T and F instead which stand for true and false.
The code for this is only 3 lines and with it we can use our homemade ternary operator wherever we wish.
Why inline?
The java ternary operator is made up of two parts, ? and :, but there’s no way to write a single function with two parts, so we must write two functions and a class to hold the results of the first function for the second function. Those picky about performance may complain that there is some overhead to this. And indeed there is. I have some hope that a future version of Kotlin might add more powerful tools that could solve this, but until then it’s an unfortunate drawback. I’ve opted to inline the functions to reduce the performance overhead, though at the cost of additional bytecode. You may remove the inline keywords if you don’t wish to make that trade. However, whether you use these functions inlined or not, the difference between using this versus Kotlin’s idiomatic if/else isn’t very noticeable outside of a very performance sensitive program.
But wait… there’s a problem
We have achieved our goal of matching the syntax of Java’s ternary operator, but there’s a non-obvious problem with the above version of T/F when it comes to functionality. The value in message
will correctly be either Success
or Failure
depending on the condition, and in all cases, the result outputed will be what you expect. But Java’s ?/: and Kotlin’s if/else have the power of control flow. They create separate branches that are only evaluated based on the condition given.
The following example demonstrates the problem. If we pass functions into the above version of T/F, because no separate branches or scopes have been created, both functions are evaluated despite the condition.
// Both "Success" and "Failure" always get printed
isSuccess T printSuccess() F printFailure()
Normally, this would be expected behavior. After all, functions should get executed when they are called. But it is unintuitive and unwanted behavior here because we are trying to match the functionality of Java’s ternary.
Supporting Lazy Evaluation of Arguments
This is not ideal, but we can roughly remedy this by adding higher-order overloading functions.
Now, if we want the arguments to be lazily or conditionally evaluated, then we can wrap them in a pair of curly braces.
// Only one message is printed
isSuccess T { printSuccess() } F { printFailure() }
Success! But alas, this is now a compromise towards matching the syntax of Java’s ternary. We don’t get lazy evaluation out of the box and must add {}, wrapping the desired arguments in functions, if we want it.