Tuesday, 31 December 2013

3/4: The dumb things ColdFusion does

G'day:

Admission:

This is the article I alluded to a coupla weeks ago when I said this:

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
struct [empty]
MEANINGOFLIFE42
THX1138

And on Railo:

Scope
MEANINGOFLIFE
number42
THX
number1138

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
local - struct [empty]
MEANINGOFLIFE42
THX1138
variables - struct
F
variables - function 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
variables - function f
MEANINGOFLIFE42
THX1138
VARIABLES
variables - struct [empty]

Note two things:
  1. variables.meaningOfLife and variables.THX did not get cleared;
  2. a new variables-scoped variable, variables.variables, has been created.
I also then tried the request scope:

// request.cfm
function f(){
    request.meaningOfLife = 42;
    request.THX = 1138;
    request = {};
    writeDump(var=request, label="request");
    writeDump(var=variables, label="variables");
}
f();

request - struct
cfdumpinitedfalse
meaningoflife42
thx1138
variables - struct
F
variables - function f
REQUEST
variables - struct [empty]

(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
Scope
MEANINGOFLIFE
number42
THX
number1138
variables
Scope
F
Public Function f
source:C:\webroots\railo-express-4.1.x-jre-win64\webapps\www\shared\git\blogExamples\cfml\scopes\local\cannotreset\local.cfm
local
Struct

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
cfdumpinitedfalse
meaningoflife42
thx1138
variables - struct
F
variables - function f
REQUEST23

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

LOCAL is explicit scope in ColdFusion 9.

You can only assign LOCAL to a struct. You cannot assign LOCAL to java.lang.String

(Note: I'm running ColdFusion 10, not ColdFusion 9. Perhaps they mean "as of ColdFusion 9").

Again, Railo handles it sensibly / uniformly:

local
Scope
MEANINGOFLIFE
number42
THX
number1138
variables
Scope
F
Public Function f
source:C:\webroots\railo-express-4.1.x-jre-win64\webapps\www\shared\git\blogExamples\cfml\scopes\local\cannotreset\local2simple.cfm
local
number23


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