Admission:
This is the article I alluded to a coupla weeks ago when I said this:
OOOPS. Got about 30sec away from pressing "publish" on a blog article... only to realise I was wrong in what I was saying. Dammit.
— Adam Cameron (@dacCfml) December 13, 2013
I promised Gavin I'd still release the article anyhow, so here it is. I have made it clear where I go wrong, so people don't get the idea I'm actually right in what I say here.
- Adam 31/12/2013
It's standing-room only on this Tube, but I've got enough space to type on my phone, so here's a quick article to make sure y'all aware of a dumb ColdFusion quirk that Adobe built into ColdFusion 9. Not an Easter Egg: more a Xmas Turd.
Prior to ColdFusion 9, one needed to explicitly VAR all one's local variables in a function. EG:
function f(){
var meaningOfLife = 42;
var THX = 1138;
return meaningOfLife * THX; // yeah, I dunno why one would want to multiply those together either
}
Because this all seems like too much typing or something, people took to doing this instead:
function f(){
var local = {};
local.meaningOfLife = 42;
local.THX = 1138;
return local.meaningOfLife * local.THX;
}
(Ignore the fact that that is actually more typing... a lot of other people sure did, when using this technique).
IE: VAR a single struct variable, and then treat it as a pseudo function-local scope. This works fine, but always struck me as being a bit lazy, as well as causing a lot of clutter in the code. I'd - personally - prefer to not have to "scope" everything. But to each their own, and it was a very common practice.
Along comes ColdFusion 9, and Adobe finally decide to give the function-local scope a name: local.
So now if one wants to make a function-local variable, one can just go like this:
function f(){
local.meaningOfLife = 42;
local.THX = 1138;
return local.meaningOfLife * local.THX; // scoping is optional on these once they're declared
}
But there was a minimal inconsequential problem. Someone had some code in which they had a pseudo-local-scope struct, and they were doing something daft with it. I don't recall exactly what they were doing, but their code behaved differently on CF8 then it did on ColdFusion 9. Instead of going "well: them's the breaks, sorry", somehow Adobe decided they had a backwards-compat issue and needed to fix it in ColdFusion, rather than just telling the person to fix their code. They handled a minor issue that a minority of people would have, by baking some stupidity into ColdFusion.
What they did was to make it that if one ever set local to be a new struct... then ColdFusion simply ignores it. No, really.
Here's where I am wrong, and I continued to explain the situation. Unfortunately for me I was on the Tube and standing up and writing this on my phone, so I could not run the code I was using to demonstrate my point. If I had been able to, I would have quickly spotted my mistake.
- Adam 31/12/2013
Here's an example of what I mean:
// local.cfm
function f(){
local.meaningOfLife = 42;
local.THX = 1138;
local = {};
writeDump(local);
}
f();
On the face of it, one would expect an empty struct there, perhaps. Well I would have, anyhow. But here's the output:
struct | |||
---|---|---|---|
ARGUMENTS |
| ||
MEANINGOFLIFE | 42 | ||
THX | 1138 |
And on Railo:
Scope | |||
MEANINGOFLIFE |
| ||
THX |
|
Hmmm. So I had got the idea that ColdFusion was simply ignoring the reassigning of the local scope to be a new struct was being ignored. Not so. Let's look more closely:
// local.cfm
function f(){
local.meaningOfLife = 42;
local.THX = 1138;
local = {};
writeDump(var=local, label="local");
writeDump(var=variables, label="variables");
}
f();
And this... um... outputs... [voice trails off in confused despair, not believing what's on his screen...]
local - struct | |||
---|---|---|---|
ARGUMENTS |
| ||
MEANINGOFLIFE | 42 | ||
THX | 1138 |
variables - struct | |||
---|---|---|---|
F |
|
W... T... F...
OK, it seems I was right in what I was going to say after all. I am sooooo confused now. If it's not obvious, none of this is rehearsed... I am writing this article, the code, and running it all as I go. I really am very confused round about now!
- Adam 31/12/2013
Um. OK. So the initial assertion I was going to make in this article was that local = {} is ignored by ColdFusion. Then I started writing some test code, beginning with a control, testing the variables scope instead of the local scope:
// variables.cfm
function f(){
variables.meaningOfLife = 42;
variables.THX = 1138;
variables = {};
writeDump(var=variables, label="variables");
}
f();
This outputs this:
variables - struct | |||
---|---|---|---|
F |
| ||
MEANINGOFLIFE | 42 | ||
THX | 1138 | ||
VARIABLES |
|
Note two things:
- variables.meaningOfLife and variables.THX did not get cleared;
- a new variables-scoped variable, variables.variables, has been created.
// request.cfm
function f(){
request.meaningOfLife = 42;
request.THX = 1138;
request = {};
writeDump(var=request, label="request");
writeDump(var=variables, label="variables");
}
f();
request - struct | |
---|---|
cfdumpinited | false |
meaningoflife | 42 |
thx | 1138 |
variables - struct | |||
---|---|---|---|
F |
| ||
REQUEST |
|
(NB: Railo's basically the same for the variables- and request-scope examples)
So at this point I concluded - wrongly, as it turns out - that this must be what happens when one does
local = {}
too. So this is when I fell on my sword, sent the Twitter update above, and felt dumb. And shelved this article "for later", as I knew I needed to rewrite a lot of it.
So, anyway... ColdFusion does simply completely ignore the statement local = {}. It does not do what it does for any other assignments directly to a "scope", instead treating it like a variable reference instead of a scope reference, it just ignores it. Bloody stupid language.
If we run local.cfm on Railo, we see it behaves uniformly / predictably:
local | |||||||||||
|
variables | |||||||||||
|
local = {} is treated as variables.local = {}, as with other scopes.
But wait. Just when you think ColdFusion couldn't get more erratic, consider this:
// local2simple.cfm
function f(){
local.meaningOfLife = 42;
local.THX = 1138;
local = 23;
writeDump(var=local, label="local");
writeDump(var=variables, label="variables");
}
f();
What do you reckon happens here? Before I show you... here's an example with the request scope instead:
// request2simple.cfm
function f(){
request.meaningOfLife = 42;
request.THX = 1138;
request = 23;
writeDump(var=request, label="request");
writeDump(var=variables, label="variables");
}
f();
This outputs:
request - struct | |
---|---|
cfdumpinited | false |
meaningoflife | 42 |
thx | 1138 |
variables - struct | |||
---|---|---|---|
F |
| ||
REQUEST | 23 |
Fair enough. So... with the local scope? This:
The following information is meant for the website developer for debugging purposes. | |||
Error Occurred While Processing Request | |||
|
(Note: I'm running ColdFusion 10, not ColdFusion 9. Perhaps they mean "as of ColdFusion 9").
Again, Railo handles it sensibly / uniformly:
local | |||||||||||
|
variables | ||||||||||||
|
In conclusion, this is a prime example where striving for backwards-compatibility is not always a sensible thing to aim for. Adobe have made CFML just a tiny bit more shit here. They should have evaluated the case on its merits, impact to the CFML code, and how hoary the work-around needs to be. And indeed if they just create a different backwards-compat issue in the process (which they have here!). They shouldn't've done this one.
But anyway, don't forget about this when you do your CF8 to CF9/10 upgrade.
And now I'm at LHR. Hopefully I'm only 15min away from that beer... ;-)
--
Adam