Tuesday 5 February 2013

Closure and bindings and that sort of bumpf

G'day:
I was gonna put this reply on the Railo mailing list where the question mark was placed in my head, but figured I've been a slack-arse over the last week or so, so will post it here instead.

BTW: sorry for being a slack-arse, but I've been sick for the last week (just the dregs of it left now), so my attention-span for looking at computer screens is at a minimum, and all of that is occupied by my work requirements. Outside of work I've been sleeping-off this 'flu, or watching DVDs (which equates to watching the first 15min of a DVD, then resuming with the "sleeping-off this 'flu"... I'm turning into my father and his amazing ability to be put to sleep by anything).


Anyway, a question came up about closure bindings, and even after the person looked at some other blog entries and wrote their own code, they were drawing the exact opposite conclusion from my own one, so I decided to write some of me own code to clarify (at least to myself, and in this particular context) that I'm not a looney.

Bruce's comment was in this post, the important bit being:

The important thing to realize with a closure is that the entire variables scope gets duplicated at the moment you create them.    Because of that, I'm a little concerned about using them unless I could guarantee the variables scope is nearly empty - such as in an  a carefully designed CFC.

That contradicted my own understanding, so I queried his assertion, but he was adamant, and provided code to demonstrate. Now to me his code didn't demonstrate what he was saying, if anything it seemed to rather more demonstrated what I was saying, so I decided I'd rather run with my own analysis rather than try to decipher Bruce's.

When I first encountered function expressions in CFML, what I had initially thought was that when one references a parent-scoped variable within the closure code, then the closure copied that variable value, and that's why the value of the the parent variable seemed to "stick" with the closure code.  EG:

// THIS CODE IS NOT CORRECT

variables.one = "tahi";

myClosure = function(){
    writeOutput(variables.one); // as this is "tahi" at the moment, forever will it remain "tahi" as far as the closure is concerned, as we just copied it
};

However this is not the case at all: no copying is done here. All that happens is that the reference to "variables.one" within the closure is bound to (pointed to) the "variables.one" in the parent's context. So wherever myClosure() ends up being executed... its "variables.one" will reference that variable "variables.one" in that original context that myClosure() was defined in.

If you are going "huh?" I would not be surprised.  As I am wont to say on the Adobe Forums when people start describing code that is giving them problem, it's much easier if I just show you the code:

// test.cfm

// set the test variable in the context of the calling code
variables.when = "Initial value in the calling-code's scope";
writeOutput("variables.when as seen in mainline code: #variables.when#<br />");


// create two functions: one via an expression (so it's a closure), one via a function statement

// this includes a reference to variables.when, which is bound *now*, so will continue to reference the variable above, irrespective of where it's called
myClosure = function(){
    writeOutput("variables.when as seen by myClosure(): #variables.when#<br />");
};

// by contrast this includes a reference to variables.when, but it's not bound until it's called, so we don't know WHICH "variables.when" it is referring to yet: it'll be determined @ call time
function myFunction(){
    writeOutput("variables.when as seen by myFunction(): #variables.when#<br />");
}



// create a test object. We use an object for testing as it has its own variables scope, so we can check which context the two functions have their variables.when references bound
o = new Test();

o.runIt(myClosure);        // the closure will see the variables.when as per the code it was defined in
o.runIt(myFunction);    // however the function will see it from the context of where it's called: as it's called from within the CFC instance, it sees the CFC instances's variables.when
o.testIt();                // and this is just a normal function within Test.cfc, by way of a control

// end of first test round
writeOutput("<hr />");


// now demonstrate that myClosure()'s reference to variables.when is a binding, not a duplicate
variables.when = "Updated value in the calling-code's scope";
writeOutput("variables.when as seen in mainline code: #variables.when#<br />");

o.runIt(myClosure); // if it was using a duplicate, then we'd still see the original value being output, but we don't

// as a control, run these again too: no change
o.runIt(myFunction);
o.testIt();

The code sets variables.when, then creates a closure that closes over that variable (by referencing it in its code), so makes a binding to it (but, I suspect, not a duplicate of it). It also creates an identical non-closure function which does exactly the same thing (code-wise). The functions simply output the current value of whichever variables.when they have bound. These functions are passed into a CFC which has a method which simply executes the function (see below for Test.cfc's code). To further demonstrate that there's no duplication of variables going on, we then update the calling code's variables.when, and runs the test a second time.


Here's Test.cfc. It sets its own variables.when so we can see which function references that instead of the one in the calling code, as well as having a method which runs whichever function it is passed, and has another method which simply outputs variables.when:

// Test.cfc
component {

    // put something in the CFC's variables scope as a failsafe
    variables.when = "Value set in object's variables scope";
    writeOutput("variables.when as seen in CFC pseudo-constructor code: #variables.when#<br />");

    // this runs whichever function is passed into it
    public void function runIt(required function f){
        f();    // bear in mind this outputs variables.when
    }

    // this is just a vanilla function, used as an experimental control
    public void function testIt(){
         writeOutput("variables.when as seen by testIt(): #variables.when#<br />");
    }

}

The output of all this is:

variables.when as seen in mainline code: Initial value in the calling-code's scope
variables.when as seen in CFC pseudo-constructor code: Value set in object's variables scope
variables.when as seen by myClosure(): Initial value in the calling-code's scope
variables.when as seen by myFunction(): Value set in object's variables scope
variables.when as seen by testIt(): Value set in object's variables scope


variables.when as seen in mainline code: Updated value in the calling-code's scope
variables.when as seen by myClosure(): Updated value in the calling-code's scope
variables.when as seen by myFunction(): Value set in object's variables scope
variables.when as seen by testIt(): Value set in object's variables scope

Note that the closure continues to reference the current value of the calling code's variables.when; it doesn't make a duplicate of it, it is simply referencing the same variable.

And, predictably, the passed-in non-closure function and the CFC's own function simply reference the version of variables.when which is in the context that the function is being called in (in this case, the variables.when of the CFC instance).

That's all pretty much what I expected to happen, so I'll refer this back to Bruce to see if we're just speaking at cross-purposes.

--
Adam