ColdFusion 10 Beta - Closures And Components And The THIS Scope
The last couple of days have been really exciting as we've been exploring the new Closures and Function Expressions that have been added to ColdFusion 10. First, we looked at the general structure and behavior of closures and function expressions; then, we looked at how closures interact with threads; now, I'd like to take a quick look at how closures interact with ColdFusion components (CFC).
THIS Is Never That Simple - Runtime Execution Context Bindings
When you invoke a function, it gets invoked in an "execution context." This context is defined by three different characteristics:
- Variables environment
- Lexical environment
- This-scope binding
As we've seen in the last few blog posts, the lexical environment is quite powerful when it comes to closures. It allows us to retain references to define-time values, even after our closures have been passed out of scope. Up until now, we've not had to worry about any THIS-scope binding since all of our code has been written without ColdFusion components. If I've learned anything from JavaScript, however, it's that the THIS scope is never as simple as you'd like it to be. For this post, I want to explore the binding of the THIS scope when closures are used in conjunction with ColdFusion components.
For the first test, all I want to do is see how the THIS and VARIABLES scope are bound to a closure that is defined inside a ColdFusion component. When that closure is passed out of the component, what can it see? To investigate, I've created a simple ColdFusion component that defines an accessor and returns it to the calling context:
Simple.cfc - Our Simple ColdFusion Component
component
output = "false"
{
// Before we return our closures, let's set some values in the
// public and private scopes.
this.value = "I am in the THIS scope!!";
variables.value = "I am in the VARIABLES scope!!";
// I return a closure that attempts to access the THIS and
// VARIABLES of the current component. We can test to see how
// these are bound at runtime.
function getClosure(){
// Define a local version of the "THIS" scope. This is a
// common practice in languages like JavaScript where the
// THIS scope is dynamically bound at method invocation time.
// THIS is treated differently than other variables... is the
// same true in ColdFusion??
var self = this;
// Create our closure accessor.
var getter = function( scope, key ){
// Check the target scope for access.
if (scope == "this"){
return( this[ key ] );
} else if (scope == "variables"){
return( variables[ key ] );
} else {
return( self[ key ] );
}
};
// Return the new closure accessor function.
return( getter );
}
}
As you can see, this ColdFusion component defines the closure, getter(). This getter then provides tunneling access to the This scope, the Variables scope, and the "Self" scope. Self is just a local variable that points to the This scope. If you've done any coding in JavaScript, you've probably come across this pattern at some point. Since the This scope is dynamically bound in JavaScript, people often create a "self" variable to retain a reference to a specific version of This.
Now, let's create some external code that makes use of this getter():
<!---
Start a CFScript block. Closures can only be used inside
of CFScript due to the syntax required to define them.
--->
<cfscript>
// Create an instance of our new component and access the
// closure "Tunnel" that we've tried to create.
getter = new Simple().getClosure();
// Access the public and private values.
writeOutput( "Public: " & getter( "this", "value" ) );
writeOutput( "<br />" );
writeOutput( "Private: " & getter( "variables", "value" ) );
writeOutput( "<br />" );
writeOutput( "Self: " & getter( "self", "value" ) );
</cfscript>
Here, we are requesting the closure from our Simple.cfc instance. We are then using that closure to access the "value" property from each of the three scopes. When we run this code, we get the following page output:
Public: I am in the THIS scope!!
Private: I am in the VARIABLES scope!!
Self: I am in the THIS scope!!
As you can see, the closure, defined within the ColdFusion component, retained a reference to the appropriate This and Variables scope even when that closure was passed out of context.
But, the closure was defined in a context in which "This" is a meaningful value; and it was then passed into a context in which "This" is not a meaningful value. As such, we can't be quite convinced that the This scope binding was truly put to the test. In order to put this binding through the wringer, we have to pass the closure into a context in which the This scope binding is meaningful. In other words, we have to pass the closure into another ColdFusion component.
For this experiment, I've created another simple ColdFusion component that does nothing but provide potential override values for the This-scope and Variables-scope:
Simple2.cfc - Our Secondary ColdFusion Component
component
output = "false"
{
// For this test, we'll SET the methods from the outside.
// But, we want to see if our local values will override the
// closure value bindings.
this.value = "I am in the OVERRIDE THIS scope.";
variables.value = "I am in the OVERRIDE VARIABLES scope!!";
// GETTER will be injected {here}.
}
As you can see, this component does almost nothing. That's because our calling code is going to be injecting the above-mentioned getter() method into this new component context. Once the getter() method becomes a property of this secondary component, we can really put the This-binding to the test:
<!---
Start a CFScript block. Closures can only be used inside
of CFScript due to the syntax required to define them.
--->
<cfscript>
// Create an instance of our new component and access the
// closure "Tunnel" that we've tried to create.
getter = new Simple().getClosure();
// Now, let's create out second component, that has not methods.
override = new Simple2();
// Store the getter closure IN the override component. We just
// want to see if the transfer does anything with the runtime
// bindings of scopes.
override.getter = getter;
// Now, let's try to access the public and private scopes from
// the FIRST component when calling the accessor ON the SECOND
// component.
writeOutput(
"Public: " & override.getter( "this", "value" )
);
writeOutput( "<br />" );
writeOutput(
"Private: " & override.getter( "variables", "value" )
);
writeOutput( "<br />" );
writeOutput(
"Self: " & override.getter( "self", "value" )
);
</cfscript>
Here, we are repeating the same scope-access test as before; only this time, we are performing it after the closure was transfered from one This-relevant context into another This-relevant context. When we run the above code, we get the following output:
Public: I am in the OVERRIDE THIS scope.
Private: I am in the VARIABLES scope!!
Self: I am in the THIS scope!!
Here's where things start to get a bit more interesting. If you look at the first line of output, you'll notice that the public value - the This-scoped value - came out of the second ColdFusion component, not the first. This means that when the closure was executed in the context of the second component, the runtime binding of THIS was bound to the second component, not to the lexical environment of the closure.
"This" is a magical scope. Its binding is performed just-in-time as functions are executed. We can't depend on the lexical scoping of our closures to retain the appropriate reference. If we need to reference the This scope, we can always use the "Self" pattern. As you can see from the above output, our locally-defined Self reference was able to retain the original This scope reference, even when the This scope was being dynamically bound.
Closures are pretty awesome. They have the ability to retain references to their lexical environment even when they are passed out of scope. This is true even for the define-time page context and the Variables scope. The only thing that seems to break that pattern is the runtime binding of the This scope. This aspect of the execution context is always dynamic, just as it is in JavaScript.
Want to use code from this post? Check out the license.
Reader Comments
Hey Ben, it is funny you post this as I just ran into an issue here.
http://tylerclendenin.com/2012/02/coldfusion-zeus-10-beta-function-expressions-and-closures-not-quite-there/
I posted two bug reports based on those things, originally I thought that the this scope was broken when calling function expressions in a CFC's context, but your post lead me to reevaluate that and it looks to me more that the invoke function does not properly apply the context.
@Tyler,
What we really need is our friendly call() and apply() methods from JavaScript ! That would be sweet. I haven't played with the invoke() method, but I assume it's just the CFScript-based equivalent of the CFInvoke tag and uses a "String" for method name. Would be interesting to see something like:
.. kind of a thing.
And to be fair, even in JavaScript, many people argue that the this-behavior *is* a bug. I think the next version of JavaScript may actually change the way it works (or so I've heard in passing).
I'm probably behind the curve on this, but could this feature be used to call methods on a CFC dynamically?
Instead of having a syntax like
<cfset results = arrayNew(1) />
<cfset myObj = createObject("component", "cfcs.foo" />
<cfloop list="#methodNames#" index="currentMethod">
<cfset arrayAppend(results, myObj[currentMethod]() />
</cfloop>
Instead we could define a getMethod() on the cfcs.foo CFC that would use this closure syntax?
I dunno, thinking out loud here. But if we could pull a reference to a CFC method dynamically, that would definitely make some things more interesting and potentially more elegant...