I realized that there is a very clean way to express a multi-clause if statement by composing ternary conditional operators like this:
var result =
condition1 ? result1
: condition2 ? result2
: condition3 ? result4
...
: conditionN ? resultN
: default;
Traditionally, this would be written in a much more verbose way:
MyType result; if (condition1) result = result1; else if (condition2) result = result2; else if (condition3) result = result3; ... else if (conditionN) result = resultN; else result = default;
Here is a simple real-world application of this trick:
string commentCount = n == 0 ? "no comments" : n == 1 ? "1 comment" : n < 100 ? n + " comments" : "100+ comments";
I really like this pattern because the code is very concise and clean. I am surprised that I have never seen it used anywhere.
Hugh Brown suggests an alternative way to rewrite the above code sample, which also nests conditional expressions:
string commentCount = string.Format("{0} comment%s", (n == 0 ? "no" : n < 100 ? n.ToString() : "100+"), (n == 1 ? "" : "s"));
Gotchas
Suprisingly, I don’t believe there are any major ones. The conditional operator has a very low operator precedence in C#, Java and C++. In C# and Java, only the assignment operators (=, +=, <<=, etc) have a lower precedence than the conditional. In C++, you also have to be cautious around the comma operator, but you should be using that construct rarely anyways.
If you really want to mix the switch expression with assignment operators, other conditionals, or even the C++ comma operator, use brackets to ensure that the conditional operators which are part of the switch expression will be applied last.
In all other cases, the pattern should behave as you’d expect.
Comments and Conclusion
It is great to find a neat trick in the good old C-based languages. Not only functional languages are cool.
Any thoughts? Has anyone seen this pattern before? Let me know in the comments.
Bodaniel Jeanes mentions a loosely-related trick with a switch statement. Note that this works in C, but not in C# or Java:
switch (true) { case n == 0: // do something break; case n > 2: // do something else break; default: return; }
Tags: C#
Nice feature :). Though it’s not usable for multiline actions. Actually it’s a self-made implementation of conditional switch that is present in most functional languages
It’s certainly looks nicer than using if … else … else … etc. Where possible. I’d really want to use a switch statement instead.
You might also want to look at Fowler’s refactoring catalog at http://www.refactoring.com/catalog/ – particularly http://www.refactoring.com/cat.....phism.html
If you’re working on a largish system, that’s the best way to go.
This construct is less powerful than a switch statement because each branch must be a single expression. But, it is more powerful in another respect because the condition is an arbitrary boolean expression, rather than just a value to compare against.
As always, different constructs fit different scenarios. The same goes for the polymorphism solution suggested by Karl.
Personally I would stick with the if/else structure even for single line statements just to maintain code consistency (although I agree it is rather verbose).
I wonder what the performance of this would be in comparison with the regular if else usage.
Vikas: That is an interesting thought. I would not expect there to be a performance difference. The conditional operator does not evaluate both the *if* and the *else* part of the expression – only the one that it needs to. Since the compiler could conceivably translate the code from one implementation to the other, there is no unavoidable reason why one of the implementations should be faster. In practice, there may be performance differences depending on how a particular compiler happens to compile the code, though.
Igor,
It’s not necessarily less powerful than a switch statement… A tactic i’ve used before is
switch(true)
{
case n == 0:
// do something
break;
case n > 2:
// do something else
break;
default:
return;
}
However, I can’t confirm this works in *all* C-based languages. However, the ternary tactic is nice when each condition block is setting the same variable because it allows you you to perform the assignment then test for the value to assign.
Bodaniel: that is a pretty cool trick!
It does not work in C#, though. C# requires the case labels to be constants.
Still, it is clever and I like it!
hmmm….that last result could not be “100+ comments”
It’ll line up better if you move the equals sign from the assignment down to align with the colons.
This is a surprisingly common pattern. I’ve seen it often with a simple return statement:
return (n == 1) ? true : false;
Although that example is rather silly.
Or maybe:
string commentCount =
string.Format(“{0} comment{1}”,
(n == 0 ? “no”
: n
Do I have to escape my code as HTML? Okay.
Maybe this:
string commentCount =
string.Format(“{0} comment%s”,
(n == 0 ? “no”
: n < 100 ? n.ToString()
: “100+”),
(n == 1 ? “” : “s”);
or this:
string commentCount =
(n == 0 ? “no”
: n < 100 ? n.ToString()
: “100+”) +
” comment” +
(n == 1 ? “” : “s”);
Clean is a relative term. :). It all boils down to the formatting really. if i were to reformat the code into something else it wouldn’t look that clean on the other hand if/elseif depending upon the formatting can be written in clean ways.
[Admin note: Harris later corrected the following part of the comment]
Also note that it is not necessary for a ternary statement to assign something.
You could always do:
(condition1) ? resultvar = resultval : ( (condition2) ? return 1 : default;
So you can see using it that way gives you other possibilities as well and you could take the program flow in which ever direction you want instead of having to return something for each branch.
Harris,
I agree that formatting is very important for this particular pattern.
Crazy what you can do with C++… Pretty cool. C# actually requires the clauses to be expressions, so you cannot use statements like “return 1″. I would expect Java to behave similarly.
Igor
Finally!
I remember i posted the quote somewhere couldn’t get back to the website. CORRECTION IS IN ORDER: the sample code in my previous comment is not valid c++ contrary to my beliefs before. I was pretty sure I had used that somewhere before but obviously not as i tried using the syntax above again and well the compiler denied me a twinkie. Igor, I would appreciate it if you could delete my comment and spare some sane people from some misleading remarks and conjured up syntax :). Thank You.
Thanks for coming back to clear it up, Harris.
It is also possible that some C++ compilers accept and some reject that pattern. So perhaps you really did write that code in the past.
This form actually appears in K&R’s original C Programming language book, except they put the colon at the _end_ of the previous line, e.g.
const char *number =
i < 0 ? "negative" :
i == 0 ? "zero" :
i == 1 ? "one" :
i == 2 ? "two" :
"lots!" ;
(there was specific indentation there that made it much more readable, which the “code” tag did not preserve)
Awesome! As a ruby programmer loved the entire post
Just tested Ruby’s (v1.8) case-when and works the same:
case(true)
when 1>2
p 1
when 1 2
Sorry, missed the code tag.. again the switch from ruby:
case(true)
when 1>2
p 1
when 1<2
p 2
end
#= 2
[…] was reading Igor Ostrovsky blog on A neat way to express multi-clause if statements in C-based languages and realized this is indeed a "neat way" in Perl as […]