Sunday, 24 November 2013

Can a function expression circumvent closure?

G'day:
I'm stumped. I "need" to have a function expression that can reference the context it's being executed in as well as the context it was declared in. And I can't work out how. I suspect this is because of one of two reasons:
  1. I'm thick;
  2. it's not possible.
(I am not suggesting those are mutually exclusive options).

[my mate has just reminded me the Ireland v All Blacks match started at 2pm, not the 2:30pm kick-off I thought. I've missed the first 5min of the game... or not, as BBC2 lets one rewind live broadcasts. So I'm writing this whilst watching the rugby]

Just to contextualise things, consider this code:

// C.cfc
component {
    variables.someVar = "Set in the object's variables scope";
}

And some code that uses that component:

// injectDeclaredFunction.cfm

variables.someVar = "Set in calling code's variables scope";

o = new C();

function dumpVariables(){
    writeDump(var=variables);
}

o.dumpVariables = dumpVariables;
o.dumpVariables();

If I run this, I get this output:

struct
SOMEVARSet in the object's variables scope
THIS
component shared.git.blogExamples.closures.executionTimeBinding.C
METHODS

[6min: Ireland score! 7-0. I had just sent a Twitter status update that I have found myself cheering for Ireland instead of New Zealand, too. Blimey]

We see the contents of the variables scope of the CFC instance because - apologies if I use the wrong technical terms - the reference to "variables" is not bound until execution time, and when dumpVariables() is executed, the context of "variables" is that of the object the function is being called within.

On the other hand, here's an example using a function expression instead of a function declaration:

//injectFunctionExpression.cfm

variables.someVar = "Set in calling code's variables scope";

o = new C();

dumpVariables = function (){
    writeDump(var=variables);
};

o.dumpVariables = dumpVariables;
o.dumpVariables();

[13min. Ireland score (and convert) again. Bloody. Hell. 14-0. They're making their own luck, and owning the All Blacks!]

And this time we get this output:

struct
DUMPVARIABLES
closure _CF_ANONYMOUSCLOSURE_0
Arguments:none
ReturnType:Any
Roles:
Access:public
Output:
DisplayName:
Hint:
Description:
O
component shared.git.blogExamples.closures.executionTimeBinding.C
DUMPVARIABLES
closure _CF_ANONYMOUSCLOSURE_0
Arguments:none
ReturnType:Any
Roles:
Access:public
Output:
DisplayName:
Hint:
Description:
SOMEVARSet in calling code's variables scope

This time, because function expressions implement closure, the reference to the variables scope in the function expression is bound at the time the function expression is defined, not when the function is called.

[19min. They've only gone and intercepted and scored what must be an 80m try. Miss the conversion. 19-0. OK, this is getting difficult for the ABs to come back from now. Wow]

This is fine for most situations, and indeed it's one of the powers of function expressions. However I need to get a function defined via a function expression to be able to reference its execution-time context, not its define-time context, and I'm struggling to work out how. Basically I need to make a reference to the CFC's own variables scope. Because I want to put something in it.

Not that it's a possibility for my situation, but the first time I'm trying to work around this is to see what happens if I reference variables that don't exist in the context that the function is defined in:

// C.cfc
component {
    variables.someVar = "Set in the object's variables scope";

    variablesReference = variables;
}

And now I reference variablesReference in the function expression:

dumpVariables = function (){
    writeDump(var=variablesReference);
};

[26min. NZ finally scores a pretty bloody good try. 19-7]

[I sit, gobsmacked]

[33min. Ireland penalty close in. Easy for Sexton: 22-7]

OK, eyes away from the telly for a sec, and I run that code:

Variable VARIABLESREFERENCE is undefined.


The error occurred inC:/apps/adobe/ColdFusion/10/cfusion/wwwroot/shared/git/blogExamples/closures/executionTimeBinding/injectFunctionExpression.cfm: line 9
7 : 
8 : dumpVariables = function (){
9 :  writeDump(var=variablesReference);
10 : };
11 : 

Resources:
Browser  Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.57 Safari/537.36
Remote Address  0:0:0:0:0:0:0:1
Referrer  http://localhost:8500/shared/git/blogExamples/closures/executionTimeBinding/
Date/Time  24-Nov-13 02:45 PM
Stack Trace
at cfinjectFunctionExpression2ecfm870422569$func_CF_ANONYMOUSCLOSURE_0.runFunction(C:/apps/adobe/ColdFusion/10/cfusion/wwwroot/shared/git/blogExamples/closures/executionTimeBinding/injectFunctionExpression.cfm:9) at cfinjectFunctionExpression2ecfm870422569.runPage(C:/apps/adobe/ColdFusion/10/cfusion/wwwroot/shared/git/blogExamples/closures/executionTimeBinding/injectFunctionExpression.cfm:13) 

Initially I thought this error was cropping up as soon as the function was defined. However I then look more closely, and note that the error is actually bubbling up from when the function was actually called. Line 13 is this one:

o.dumpVariables();

[H/T: 22-7. I noticed the first Irish cock-up at about the 38min mark, but they are all over the All Blacks at the moment. Richie McCaw has a look of utter disbelief on his mug, coming on after the break]

So what this confirms for me is something I had never checked before: it's not that the references within the function code block are evaluated when the function is defined... this is not done until the function is called, but the bindings to - I guess - all the scopes are done when the function is defined.

Something occurs to me.. what if I do this:

http://localhost:8500/shared/git/blogExamples/closures/executionTimeBinding/injectFunctionExpression.cfm?variablesReference=Set%20on%20the%20URL

So this will define URL.variablesReference. If I was to reference just variablesReference in the calling code, this would find the URL-scoped variable due to the way CF resolves unscoped variables. But I wasn't too sure how this would have been handled in a closure situation. Exactly what is being "enclosed" when a function expression is defined? Well the output is now:

Set on the URL

So it seems it's not just the variables scope that is closed-over in the function expression, it's... everything? (I knocked out a quick form and tested the form scope too: same).

[49min. All Blacks get over the line, but the ball is held up. Still 22-7]

[52min... but at least they get a penalty: 22-10]

OK. So I need to somehow do something which will not bind variable references until the function is called. Or to somehow reference the CFCs variables scope by some mechanism other than via  its name (variables references the calling code's variables scope), or an unscoped reference which still does the scope look-up as if it's being done in the calling code too.

[64min. All Blacks finally get over the line... and convert... 22-17. I'd be worrying if I was Ireland now... the ABs have got momentum. And the Irish have a habit of dropping of in the last quarter...]

I've tried a few tricks that really made no sense to try, but I'm at that stage of trying random stuff (I usually advise that it's at this point one should just stop trying, and do something else for a while):

dumpVariables = function (){
    writeDump(var=structget("variables.someVar"));
};

Still finds the calling-code's variables scope. As does this:

dumpVariables = function (){
    writeDump(var=evaluate("variables.someVar"));
};

[Sexton misses a kick he really should have got to give Ireland some breathing space. Still 22-17. 6min to go. COME ON IRELAND]

Basically as far as I can tell, the function expression still really resides in the calling code's context, and there's simply gonna be no way to make its code aware that it's being run from inside a CFC instance.

[please hold on for a bit... 2min to go, Ireland look to have beaten the All Blacks... 1min... 0min... All Blacks play out of their own half after the clock hits 80min, and... and... scores a fantastic try to even the scores in the 82nd min. First attempt at conversion - which misses - charged down prematurely by Ireland, so they have a second take at it... which sails over. All Blacks win 22-24. If Sexton had got that penalty, Ireland would have won 25-24. Damn. All Blacks become first top-tier rugby team to win every single match in the calendar year. Very impressive for the All Blacks, but I am really really gutted for Ireland. I thought they'd won that one]

The only thing I can think to some how leverage is the fact that the this scope when the function is called does understand it's inside the object:

// C.cfc
component {
    variables.someVar = "Set in the object's variables scope";
    this.lookForThis = "Public variable set in object's this scope";
}

// dumpThis.cfm

o = new C();

dumpThis = function (){
    writeDump(var=this);
};

o.dumpThis = dumpThis;
o.dumpThis();

Yields:

component shared.git.blogExamples.closures.executionTimeBinding.C
DUMPTHIS
closure _CF_ANONYMOUSCLOSURE_0
Arguments:none
ReturnType:Any
Roles:
Access:public
Output:
DisplayName:
Hint:
Description:
LOOKFORTHISPublic variable set in object's this scope

But how I can leverage that (erm... this.... ;-) to get at the object's variables scope... I have no idea.

Any ideas?

In the mean time I'm just gonna do what I need to do in the object's this scope... it's not ideal, but it's not completely wrong for what I need to do, so I can live with that whilst I mull things over.

Now I shall read what people have had to say about the rugby...

--
Adam