There are cases where gotos are good (most possible uses of gotos are not good). I needed to write JavaScript functions (for running in NodeJS) where I wanted to call the callback function just once in the end (to make things as clear as possible). In C that would be (this is a simplified example):
void withgoto(int x, void(*callback)(int) ) {
int r;
if ( (r = test1(x)) )
goto done;
if ( (r = test2(x)) )
goto done;
if ( (r = test3(x)) )
goto done;
r = 0;
done:
(*callback)(r);
}
I think that looks nice! I mean the way goto controls the flow, not the syntax for function pointers.
JavaScript: multiple callbacks
The most obvious way to me to write this in JavaScript was:
var with4callbacks = function(x, callback) {
var r
if ( r = test1(x) ) {
callback(r)
return
}
if ( r = test2(x) ) {
callback(r)
return
}
if ( r = test3(x) ) {
callback(r)
return
}
r = 0
callback(r)
}
This works perfectly, of course. But it is not nice with callback in several places. It is annoying (bloated) to always write return after callback. And in other cases it can be a little unclear if callback is called zero times, or more than one time… which is basically catastrophic. What options are there?
JavaScript: abusing exceptions
My first idea was to abuse the throw/catch-construction:
var withexceptions = function(x, callback) {
var r
try {
if ( r = test1(x) )
throw null
if ( r = test2(x) )
throw null
if ( r = test3(x) )
throw null
r = 0
} catch(e) {
}
callback(r)
}
This works just perfectly. In a more real world case you would probably put some code in the catch block. Is it good style? Maybe not.
JavaScript: an internal function
With an internal (is it called so?) function, a return does the job:
var withinternalfunc = function(x, callback) {
var r
var f
f = function() {
if ( r = test1(x) )
return
if ( r = test2(x) )
return
if ( r = test3(x) )
return
r = 0
}
f()
callback(r)
}
Well, this looks like JavaScript, but it is not super clear.
JavaScript: an external function
You can also do with an external function (risking that you need to pass plenty of parameters to it, but in my simple example that is not an issue):
var externalfunc = function(x) {
var r
if ( r = test1(x) )
return r
if ( r = test2(x) )
return r
if ( r = test3(x) )
return r
return 0
}
var withexternalfunc = function(x, callback) {
callback(externalfunc(x))
}
Do you think the readability is improved compared to the goto code? I don’t think so.
JavaScript: Break out of Block
Finally (and I got help coming up with this one), it is possible to do:
var withbreakblock = function(x, callback) {
var r
var f
myblock:
{
if ( r = test1(x) )
break myblock
if ( r = test2(x) )
break myblock
if ( r = test3(x) )
break myblock
r = 0
}
callback(r)
}
Well, that is at close to the goto construction I come with JavaScript. Pretty nice!
JavaScript: Multiple if(done)
Using a done-variable and multiple if statements is also possible:
var with3ifs = function(x, callback) {
var r
var done = false
if ( r = test1(x) )
done = true
if ( !done ) {
if ( r = test2(x) )
done = true
}
if ( !done ) {
if ( r = test3(x) )
done = true
}
if ( !done ) {
r = 0
}
callback(r)
}
Hardly pretty, I think. The longer the code gets (the more sequential ifs there is), the higher the penalty for the ifs will be.
Performance
Which one I choose may depend on performance, if the difference is big. They should all be fast, but:
- It is quite unclear what the cost of throwing (an exception) is
- The internal function, is it recompiled and what is the cost?
I measured performance as (millions of) calls to the function per second. The test functions are rather cheap, and x is an integer in this case.
I did three test runs:
- The fall through case (r=0) is relatively rare (~8%)
- The fall through case is very common (~92%)
- The fall through case is extremely common (>99.99%)
In real applications fallthrough rate may be the most common case, with no error input data found. The benchmark environment is:
- Mac Book Air Core i5@1.4GHz
- C Compiler: Apple LLVM version 6.1.0 (clang-602.0.49) (based on LLVM 3.6.0svn)
- C Flags: -O2
- Node version: v0.10.35 (installed from pkgsrc.org, x86_64 version)
Performance was rather consistent over several runs (for 1000 000 calls each):
Fallthrough Rate ~8% ~92 >99.99%
---------------------------------------------------------
C: withgoto 66.7 76.9 83.3 Mops
NodeJS: with4callbacks 14.9 14.7 16.4 Mops
NodeJS: with exceptions 3.67 8.77 10.3 Mops
NodeJS: withinternalfunc 8.33 8.54 9.09 Mops
NodeJS: withexternalfunc 14.5 14.9 15.6 Mops
NodeJS: withbreakblock 14.9 15.4 17.5 Mops
NodeJS: with3ifs 15.2 15.6 16.9 Mops
The C code was row-by-row translated into the JavaScript code. The performance difference is between C/Clang and NodeJS, not thanks to the goto construction itself of course.
On Recursion
In JavaScript it is quite natural to do recursion when you deal with callbacks. So I decided to run the same benchmarks using recursion instead of a loop. Each recursion step involves three called ( function()->callback()->next()-> ). With this setup the maximum recursion depth was about 3×5300 (perhaps close to 16535?). That may sound much, but not enough to produce any benchmarks. Do I need to mention that C delivered 1000 000 recursive calls at exactly the same performance as the loop?
Conclusion
For real code 3.7 millions exceptions per second sounds pretty far fetched. Unless you are in a tight loop (which you probably are not, when you deal with callbacks), all solutions will perform well. However, the break out of a block is clearly the most elegant way and also the most efficient, second only to the real goto of course. I suspect the generally higher performance in the (very) high fallthrough case is because branch prediction gets more successful.
Any better ideas?