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
"heads"
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)){
return "heads";
}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
return "heads";
},
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 |
VARIABLES | calling code |
|
2 |
Within true block - struct |
LOCAL | true block |
|
Back in calling code - array |
1 | calling 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.