Friday, 22 November 2013

CFML: Slightly interesting (?) but mostly pointless exercise with inline function expressions

G'day:
I was watching Blow-up and Dead Man Down with one eye on the TV screen and one eye on the code screen last night (actually all I have is a laptop, so it was different halves of the same screen, but hey), and I came to be thinking about some Ruby, and wondered how it could work in CFML.

Yesterday one of my colleagues (Chris) said something curious: that he doesn't "approve" of the ternary operator. I think it's a fine construct if used with caution... the caution being "the resultant code must still be easy to read" (so the same as with any code, really). IE, this sort of thing is fine:

result = randRange(0,1) ? "heads" : "tails";
writeOutput(result);

This, however:

H = (
C == 0 ? null :
V == r ? (g - b) / C :
V == g ? (b - r) / C + 2 :
(r - g) / C + 4
);

Is a shooting offence. Even if one only considers the ternary operator usage, and ignores the poor variable-naming (I lifted this from Stack Overflow: "A somewhat painful triple-nested ternary operator").

But I like the notion of having an conditional statement which is an expression.

In Ruby, everything is an expression, so every statement "returns" a value. Even an if statement. One can do this:

result = if Random.new().rand(0..1) == 1
else
"tails"
end
puts result

In this case the value "returned" from an if statement is the last expression from within whichever of the true or false block was executed. So result ends up containing one of "heads" or "tails".

I thought it'd be handy to have an equivalent construct in CFML, where one could have an if() function, which would work like this:

result = if (randRange(0,1)){
}else{
return "tails";
}
writeOutput(result);

However this doesn't follow normal function syntax as it requires two code blocks (one for true, one for false), not just the usual one. So perhaps not completely straight fwd for the Railo or Adobe bods to implement.

Then it occurred to me... it's actually very bloody easy to implement in CFML, using inline function expressions. Here's a proof of concept:

function _if(required boolean condition, required function _true, function _false){
if (condition){
return _true();
} else if(structKeyExists(arguments, "_false")){
return _false();
}
}

This mimics if / if/else. It's very straight forward really: instead of having blocks, it just takes a function or two, and calls one of them based on the results of the condition.

And an example calling of this using inline function expressions demonstrates the code kinda mimics an if() block visually, but it has the benefit of returning a value:

result = _if(
randRange(0,1),     // condition
function(){         // true
},
function(){         // false
return "tails";
}
);
writeOutput("#result#<br><hr>");

One good side effect of this approach - and it's probably more handy than the construct itself - is that because the true/false blocks are functions, any function-local variables within them are indeed local to the function block itself, and do not pollute the calling scope. This would help keeping the variables-scope uncluttered in larger tracts of code. Demonstration:

scope = "calling code";

_if(
randRange(0,1),
function(){
var scope = "true block";
writeDump(var=[{variables=variables.scope}, {local=scope}], label="Within true block");
},
function(){
var scope = "false block";
writeDump(var=[{variables=variables.scope}, {local=scope}], label="Within false block");
}
);

writeDump(var=[scope], label="Back in calling code");

This outputs:

Within true block - array
1
Within true block - struct
VARIABLEScalling code
2
Within true block - struct
LOCALtrue block
Back in calling code - array
1calling code

See how the function-local scope variable a) didn't interfere with the calling-code's variable; b) is gone after the function exits.

This could be beneficial.

JavaScript has a concept of  self-executing anonymous functions, which are handy for much the same reason. I've raised this as an E/R for ColdFusion, but it's been deferred: 3346435.

Continuing on this tangent, I figured I could perhaps contrive a looping construct too: one that works like a for() loop, but returns a value. And it was easy enough, using exactly the same technique:

function loop(function before, function condition, function afterEach, function body){
if (structKeyExists(arguments, "before")){
before();
}
if (!structKeyExists(arguments, "condition")){
condition = function(){
return true;
};
}
while (condition()){
var result = body();
afterEach();
}
return result;
}

The arguments here mimic how an indexed for() loop works:
• before() is called once at the beginning of the process;
• condition() is checked at the beginning of each iteration;
• afterEach() is called after each iteration;
• body() emulates the code block.
Indeed it's slightly better than a for() loop in that it allows more than one expression in each of the first three arguments, whereas the native version only permits one.

Here's a quick test:

result = loop(
function(){
sum = 0;
i=1;
},
function(){
return i <= 10;
},
function(){
i++;
},
function(){
sum += i;
return sum;
}
);

writeOutput("#result#<br>");
writeDump(var=variables);

As mentioned above, note how I'm initialising two variables here.

The output for this is:

55
struct
I11
LOOP
function loop
RESULT55
SUM55

Note how because I'm not varing those variables, they do indeed go into the variables scope, but I need them to be available across the different functions, so that's by design (/necessity). However if there were any more "worker" variables in those functions, they can be VARed away into safety, and not pollute the variables scope.

One consideration here is how does this sort of code perform? Obviously native constructs are going to be much more performant than bespoke code like this, and calling these functions will have overhead too. Let's see:

param numeric URL.iterations = 100;

start = getTickCount();
result = loop(
function(){
sum = 0;
i=1;
},
function(){
return i <= URL.iterations;
},
function(){
i++;
},
function(){
sum += i;
return sum;
}
);
end = getTickCount();

writeOutput("#result#<br>");
writeOutput("loop(): #end-start#ms<br><hr>");

start = getTickCount();
sum=0;
for (i=1; i <= URL.iterations; i++){
sum += i;
}
end = getTickCount();

writeOutput("#result#<br>");
writeOutput("for (): #end-start#ms<br><hr>");

Giving that 100000 iterations (yeah yeah, I know that loop-de-loop performance testing is daft, and I make a point of observing that every time I do one), gives this sort of thing:

5000050000
loop(): 508ms

5000050000
for (): 65ms

TBH, I'm not that fussed about that. It's over 100000 iterations, so a half-second overhead is inconsequential on that scale.

And that's about that. I dunno if I really ever would use functions like this, but it was a nice little exercise anyhow.

--