Sunday 30 March 2014

Railo: I've got to the bottom of that code I asked you to test for me

Thanks to everyone who helped out with my challenge in this article: "Railo: help me replicate a bug I'm seeing on one of my machines". I think the reason I was seeing the difference in behaviour is twofold:
  1. I'm a dropkick;
  2. Railo's setting "Local scope mode" (which I can't find docs to link to [grumble], so I'll need to explain it).
The reason why I suspect I'm a dropkick is that I think this is all down to my machine at work having "local scope mode" set to "modern", whereas every other instance I have been looking at is set to "classic". I do not recall switching my work machine to use this setting (and I'll be switching it off as soon as I sit down at it on Monday), but it seems like this is what the difference is.

Thanks to Gert for pointing me in the direction of the various Railo "make Railo work differently from ColdFusion" settings that Railo has.

Local Scope Mode

Railo has a setting which messes with how function-local variables work. In CFML by default, to declare a variable as local to a function, one needs to either var it, or explicitly declare it in the local scope (eg: = "tahi"). If one doesn't do either of these, then an unscoped variable will go in the calling-code's variables scope. So either the main variables scope of the request if the function is declared in a CFM, or in the CFC's shared variables scope if it's a function in a CFC. This was a poor decision on the part of Macromedia when they first implemented functions, but now we're stuck with it. Well we are in ColdFusion, anyhow.

In Railo they've added a setting to enable unscoped variables within functions to default to being put in the function-local scope by default. I think I might write a separate article on this so there's some documentation for it somewhere (if someone can find the actual docs, let me know: it'll save me some time).

Anyway, I cannot check the machine I'm seeing the "odd" behaviour on as it's my work PC and I am at home at the moment. I'll check it tomorrow. But I can replicate the behaviour I'm seeing on this Railo instance if I switch that setting from "classic" to "modern".


There is still a coupla bugs here. Or it might be one and a half bugs, I'm not sure.

First Bug

"Modern" mode breaks closure. Consider this code:

counter = 0;
incrementCounter = function(){
    return ++counter;
writeOutput("incrementCounter() returned: #incrementCounter()# (current value of counter: #counter#)<br>");
writeOutput("incrementCounter() returned: #incrementCounter()# (current value of counter: #counter#)<br>");

Here I set a variable counter to zero, then I use a function expression to enclose that variable so that the function expression's code references the mainline variable. So in this context, the reference to counter within the function is - by definition - supposed to be the one in the calling code. it's not supposed to be a function-local one. I can work around this by using variables.counter in the function, but I shouldn't have to: Railo should see the calling-code variable and realise I'm referencing that.

I can understand if it was a completely new variable that it would park it in the function-local scope, but not with an existing variable.

I could conceivably see that if I had another one of these "make it work differently" options Railo has - "cascading" - set to "strict" that behaviour here might be different, but even then, it's still supposed to scan the variables scope for variable references (according to the on-screen comment in the administrator where the setting is managed).

Second Bug

Even if the behaviour in the first bug was "expected behaviour", then this is still broken. If the reference to counter inside the function is function-local, then ++counter should break. Because I haven't actually created counter yet, so I can't increment it. Consider this variation of the function by way of example:

incrementCounter = function(){
    return ++localcounter;

This outputs:

Railo Error (expression)
Messagevariable [localcounter] doesn't exist
StacktraceThe Error Occurred inx
/shared/git/blogExamples/railo/bugs/iterationmethods/scopingIssueSimplified.cfm: line 4 
2: counter = 0;
3: incrementCounter = function(){
4: return ++localcounter;
5: };

Which is true, and entirely fair enough.

So it seems to me that Railo is partially spotting the variables-scoped version of counter, but then doing some weirdness and also creating a function-local version of it.

Which is wrong.

I shall update RAILO-3000 to reflect this information.

And I am now not entirely sure that these "change how CFML works" settings are a very good idea. If Railo wanna do their own language... go for it... do whatever you like. However if they have decided to do CFML, then they should accept that it has quirks like this, and leave it be. By adding ths sort of optionality in, all they're achieving is creating uncertainty. We might not like the way CFML works in regards to function-local variable scoping and scope look-ups; but we all know how it works. Unless of course Railo offers a setting to make it work differently. [Eye roll].

At least the settings are off (ie: compatibile with ColdFusion) by default.