ColdFusion 10 Beta - Closures And Function Expressions
For the last year or so, people have been very curious about the idea of adding Closures to ColdFusion 10 - code name, Zeus. Some of these people are excited about the power of closures; others are confused as to what a closure is and how a closure can even add value to the ColdFusion workflow. I, for one, love closures and every bit of value that they add to languages like JavaScript. However, as a bleeding-edge feature of ColdFusion, I understand that there will be a long exploratory period in which we, as a community, have to figure out how best to integrate the advantages that closures might offer for ColdFusion 10.
To kick this exploration off, I wanted to talk about what closures are, what function expressions are, and then look at some of the ways they can be used in ColdFusion 10. I probably won't talk too much about the pros and cons; rather, I'll just look at this from a technical standpoint first. Once the mechanics are understood, we can then start to build theory.
Function Declaration vs. Function Expression
Up until now, ColdFusion has only supported what's known as a Function Declaration. A function declaration defines a named function without the assignment of that function to a variable. Every CFFunction tag that you've written, to date, is a function declaration:
<cffunction name="doSomething"> ... </cffunction>
ColdFusion 10 allows us to start writing Function Expressions. A function expression is the definition of a function that exists as part of a larger expression. In other languages, function expressions can be either named or they can be anonymous. In ColdFusion 10, function expressions must be anonymous. ColdFusion 10 also requires function expressions (from what I can tell) to be used in a script context - use within a tag context will cause a parsing error.
So what is a "larger expression" that contains function expressions? To be honest, I can't quite define what an expression is. According to Wikipedia, an expression in programming is:
... a combination of explicit values, constants, variables, operators, and functions that are interpreted according to the particular rules of precedence and of association for a particular programming language, which computes and then produces (returns, in a stateful environment) another value.
Of course, that doesn't necessarily clear it up for me. What I can tell you is that a function definition is considered a function expression when it is simultaneously defined and:
- Assigned to a variable.
- Passed into a function (as an argument).
- Returned from a function.
- Stored in a data structure (ie. array, struct, etc.).
- Used in a control statement.
So, basically, anytime you want to use the "function" keyword outside of the CFFunction tag (and its CFScript-based equivalent), you're creating a Function Expression.
Function Expression vs. Closure
It is important to understand that a Function Expression is not the same thing as a Closure. Function expressions describe how a function can be defined; closures, on the other hand, articulate how values get bound to the function at the time at which it is defined.
Function expressions are more of a compile-time / syntax concern whereas closures are more of a run-time / scope-chain concern.
NOTE: As a refresher, you may want to see how values get bound to functions that were defined as Function Declarations.
Wrapping your head around closures is a bit of a journey. And, it's probably not completely necessary for this blog post. I would, however, recommend that you check out A Graphical Explanation Of Javascript Closures In A jQuery Context. This talks about closures in JavaScript; but, the rules of variable binding are the same in ColdFusion closures. If you understand closures in JavaScript, you'll understand them in ColdFusion 10.
And, of course, as we look at the following demos, it will hopefully become more clear as to how values get bound to functions as they are defined as function expressions.
Function Expressions As Function Arguments
Even if you completely ignore the value-binding aspects of Closures, it's easy to see the value-add of function expressions. This is especially true since ColdFusion 10 has added a number of built-in, higher-order functions that take other functions as arguments.
NOTE: A higher-order function is simply one that takes functions as arguments; or returns them as a result; or, both.
According to the ColdFusion 10 beta documentation, we now have the following closure-oriented functions:
- arrayEach( array, function )
- arrayFilter( array, function )
- arrayFind( array, function )
- arrayFindAll( array, function )
- arraySort( array, function )
- listFilter( list, function )
- structEach( struct, function )
- structFilter( struct, function )
This is pretty cool. But, if you have experience with "functional" programming, you might notice the lack of any Mapping functions - a function that maps the values in one collection on a new collection. As such, I thought a fun first demo would be to create our own arrayMap() function.
In the following code, the function arrayMap() takes a collection and a callback (which we will define as a function expression). It then invokes the callback on each element in the source array, using the return value to create a new, mapped array.
<!---
Start a CFScript block. Closures can only be used inside
of CFScript due to the syntax required to define them.
--->
<cfscript>
// I map one array onto a new array, invoking the callback
// on each target array item. If NULL is returned, no item
// is added to the new array; if an array is returned, all
// items in the array are added individually to the new
// array. All other items are added as-is to the end of the
// resultant array.
function arrayMap( targetCollection, callback ){
// Create a new array to hold resultant, mapped values.
var mappedCollection = [];
// Loop over each value in the target collection to see
// how it will be mapped onto the new array.
arrayEach(
targetCollection,
function( value ){
// Pass this off to mapping function.
var mappedValue = callback( value );
// Check to see what kind of mapping was done. If
// null is returned, then no mapping has taken place.
if (isNull( mappedValue )){
// Return out - this value will NOT be added to
// the mapped collection.
return;
} else if (isArray( mappedValue )){
// Append each item in the mapped value array
// individually onto the mapped collection.
arrayAppend(
mappedCollection,
mappedValue,
true
);
} else {
// Add any non-array value onto the mapped
// collection as-is.
arrayAppend( mappedCollection, mappedValue );
}
}
);
// Return the mapped collection.
return( mappedCollection );
}
// ------------------------------------------------------ //
// ------------------------------------------------------ //
// Create a collection of friends with various properties.
friends = [
{
name: "Sarah",
age: 44
},
{
name: "Tricia",
age: 32
},
{
name: "Joanna",
age: 38
}
];
// Now, get the average age of the friends collection. To do
// this, we will first map the friends array onto an "Age" array;
// then, we will average it.
averageAge = arrayAvg(
arrayMap(
friends,
function( friend ){
// For each friend object, map the AGE onto the
// resultant value.
return( friend.age );
}
)
);
// Output the age.
writeOutput( "Average Age: " & averageAge );
</cfscript>
As you can see, we have a list of friend objects which we are mapping onto an array of ages. Then, we use the arrayAvg() method to average those and get the following output:
Average Age: 38
As you can see, this makes use of the new arrayEach() method as well as the augmented arrayAppend() method which can now execute "appendAll" style merging. But most important of all, we are using function expressions - in this case, we are simultaneously defining functions and using them as invocation parameters.
Notice, however, that there's nothing special about our memory bindings - all values are defined and utilized in the same context. In effect, we are not yet using the "closure" aspects of this new form of programming.
Currying And The Partial Application Of Functions
Now that we've looked at the purely syntactic side of function expressions, let's take look at the power of closures and lexical binding. In this demo, we'll look at Currying. Currying is the transformation of a function which takes multiple (N) arguments into a new function which takes fewer (less than N) arguments. The removed / "partially applied" arguments are encapsulated within the new function closure.
In the following demo, we have a method, greet(), which takes a Name and a Greeting. We will then curry this function, applying a set of greetings. This new function will then, itself, be curried, applying a name. The final function can then be invoked with zero arguments.
<!---
Start a CFScript block. Closures can only be used inside
of CFScript due to the syntax required to define them.
--->
<cfscript>
// Define a function that will formulate a greeting using the
// given name and the greeting phrase.
greet = function( name, greeting ){
// Return the compiled greeting.
return( "Hello " & name & ", " & greeting );
};
// ------------------------------------------------------ //
// ------------------------------------------------------ //
// I take a greeting function that needs both a name and greeting
// phrase and I curry it such that the resultant function only
// requires a Name in order to execute. The definition and the
// selection of the greeting phrase is now encapsulated within
// the return function.
applyGreetings = function( greet, greetings ){
// I am a utility function used to select a random greeting
// from the supplied greetings.
var getRandomGreeting = function(){
// Determing the number of elements we are choosing from.
var greetingCount = arrayLen( greetings );
// Return a randomly selected greeting.
return( greetings[ randRange( 1, greetingCount ) ] );
};
// I am the new Greet function that requires only a Name.
// The greeting phrases have been built into the logic of
// the function internals.
var newGreet = function( name ){
// Invoke the original greet function using the given
// name and the randomized greeting.
return( greet( name, getRandomGreeting() ) );
};
// Return the curried function.
return( newGreet );
};
// Curry the greet function with a set of greetings.
greetRandomly = applyGreetings(
greet,
[
"very nice to meet you.",
"lovely to make your aquaintance.",
"rumors of your beauty spread far and wide, yet I see " &
"they hardly do you justice.",
"lovely weather, isn't it?"
]
);
// ------------------------------------------------------ //
// ------------------------------------------------------ //
// I take a greet function that requires ONLY a name and I curry
// it such that the resultant function no longer requires any
// arguments to invoke. The name will be encapsulated within the
// resultant function.
applyName = function( greet, name ){
// I am the new Greet function which no longer requires any
// arguments - the Name selection is built into the logic.
var newGreet = function(){
// Invoke the original greet function with the
// encapsulated name.
return( greet( name ) );
};
// Return the curried function.
return( newGreet );
};
// Curry the greet function with the given name.
greetSarah = applyName( greetRandomly, "Sarah" );
// ------------------------------------------------------ //
// ------------------------------------------------------ //
// At this point, we have created a curried function which no
// longer requires any arguments. Now, we can simply invoke this
// function on its own.
writeOutput( greetSarah() & "<br />" );
writeOutput( greetSarah() & "<br />" );
writeOutput( greetSarah() & "<br />" );
writeOutput( greetSarah() & "<br />" );
writeOutput( greetSarah() & "<br />" );
writeOutput( greetSarah() & "<br />" );
writeOutput( greetSarah() & "<br />" );
writeOutput( greetSarah() & "<br />" );
</cfscript>
As you can see, when we curry a function, we define a new function which retains access to the "applied" argument. When we apply the greeting and then apply the name, each curried function retains access to the applied value.
When we run the above code, we get the following output:
Hello Sarah, lovely weather, isn't it?
Hello Sarah, very nice to meet you.
Hello Sarah, rumors of your beauty spread far and wide, yet I see they hardly do you justice.
Hello Sarah, lovely to make your aquaintance.
Hello Sarah, very nice to meet you.
Hello Sarah, rumors of your beauty spread far and wide, yet I see they hardly do you justice.
Hello Sarah, lovely to make your aquaintance.
Hello Sarah, very nice to meet you.
Now it's starting to get more interesting! The power of lexical binding is starting to shine through.
Regular Expression Matching With Closures
As we saw above, a closure retains access to the context in which it was defined. This is totally badass because it means that even when we pass a closure out of its defining context, it can still make use of other define-time variables. One use-case for this, which I use a lot in JavaScript, is the collecting of regular expression pattern matches within a target text value.
For this demo, we'll create a new function, reReplaceAll(). Like the core regular expression replace function - reReplace() - this will replace pattern matches with values. The difference is that reReplaceAll() doesn't use a static replace value; instead, it uses a callback to define the replacement values. This means that for every match, a callback is invoked and the return value is merged into the resultant string.
That's cool, but that's not the focus of the demo - it's just another way that Function Expressions rock, hardcore style. The real point of the demo is to show that the callback that we pass into reReplaceAll() retains a handle on the define-time context; this allows us to collect the matches externally as we "replace" them in our target string:
<!---
Start a CFScript block. Closures can only be used inside
of CFScript due to the syntax required to define them.
--->
<cfscript>
// I search the given text for the given regular expression
// pattern. Then, I pass each match off to the callback in order
// to get a replacement text (returned from the callback).
function reReplaceAll( text, regex, callback ){
// Compile the Java regular expression pattern.
var pattern = createObject( "java", "java.util.regex.Pattern" )
.compile( javaCast( "string", regex ) )
;
// Create a matcher that will be able to iterate over the
// matches in the given string.
var matcher = pattern.matcher(
javaCast( "string", text )
);
// Create a string buffer to build our augmented text.
var buffer = createObject( "java", "java.lang.StringBuffer" ).init();
// Now that we have everything setup, let's start looping over
// all the matches in the text to replace them.
while( matcher.find() ){
// We'll need to build up the groups in the match in
// order to invoke the callback.
var callbackArguments = {};
// The first argument is ALWAYS the entire match.
callbackArguments[ 1 ] = matcher.group();
// Loop over each group to build up collection.
for ( var i = 1 ; i < matcher.groupCount() ; i++ ){
callbackArguments[ i + 1 ] = matcher.group( i );
}
// Invoke the callback to determine the replacement
// text for this match.
var replacement = callback(
argumentCollection = callbackArguments
);
// Make sure the value is defined -- otherwise, just set
// it to the empty string.
if (isNull( replacement )){
replacement = "";
}
// Merge the replacement value into the results buffer.
// NOTE: We are using the quoteReplacement() function so
// that special characters in the replacement text are
// escaped for the merge operation.
matcher.appendReplacement(
buffer,
matcher.quoteReplacement(
javaCast( "string", replacement )
)
);
}
// Merge any trailing, non-matching content onto the
// results buffer.
matcher.appendTail( buffer );
// Return the resultant string.
return( buffer.toString() );
};
// ------------------------------------------------------ //
// ------------------------------------------------------ //
// ------------------------------------------------------ //
// ------------------------------------------------------ //
// I use the given regular expression pattern in order to
// extract all matches from the given text. The resultant
// collection is returned.
function reExtractAll( text, regex ){
// Create a collection buffer.
var matches = [];
// Use the reReplaceAll() method in order to execute the
// actual iteration. We'll use the callback as the means
// by which to get at the individual matches.
reReplaceAll(
text,
regex,
function( $0 ){
// Add the given match to the collection.
arrayAppend( matches, $0 );
}
);
// Return the collected matches.
return( matches );
}
// Build up a demo text value.
demoText = [
"Hello there, my name is Ben. I love ColdFusion so much.",
"It's seriously awesome! I had so much trouble sleeping",
"just knowing that this baby beast was now out there.",
"So much experimenting to do!!!!"
];
// Collection all the words that being with "S".
sWords = reExtractAll(
arrayToList( demoText, " " ),
"(?i)\bs[\w]+"
);
// Output the extracted matches.
writeDump(
var = sWords,
label = "Words Starting With S"
);
</cfscript>
As you can see, we have built a function, reExtractAll() which builds on top of the reReplaceAll() function. The function expression that we pass into reReplaceAll() is able to access the the locally-declared "matches" array even after it is passed into the reReplaceAll() context. This allows our reExtractAll() function to build up the matches while the reReplaceAll() function replaces them.
When we run the above code, we get the following array output of words that start with "S":
1 - so
2 - seriously
3 - so
4 - sleeping
5 - So
Hopefully this is starting to get you excited! But, check out this next one!
Creating Light-Weight Objects With Closures
Closures allow us to bind functions to a given set of values. At a very abstract level, this is what ColdFusion components are - they are functions that are bound to a specific set of variables. With closures, we can begin to create simple, Component-like objects without the need to create an entire ColdFusion component.
NOTE: I'm not saying that we should avoid ColdFusion components - I'm simply demonstrating functionality.
In this demo, we're going to create a simple "Dictionary" object that maintains an internal cache of key-value pairs. This can only be accessed and mutated through the exposed get() and set() closures, respectively.
<!---
Start a CFScript block. Closures can only be used inside
of CFScript due to the syntax required to define them.
--->
<cfscript>
// I create a light-weight dictionary that can store key-value
// pairs that can only be access through a getter and setter.
function dictionaryNew(){
// Define the cache that will be used to store the key-value
// pairs.
var cache = {};
// Define the dictionary instance.
var dictionary = {};
// I am the accessor for the local dictionary cache.
dictionary.get = function( key ){
// Return value.
return( cache[ key ] );
};
// I am the mutator for the local dictionary cache.
dictionary.set = function( key, value ){
// Set the value.
cache[ key ] = value;
// Return the dictionary reference so that the set()
// method can be chained.
return( dictionary );
};
// Return the new dictionary with get/set properties.
return( dictionary );
}
// ------------------------------------------------------ //
// ------------------------------------------------------ //
// Create two new dictionaries.
tricia = dictionaryNew();
sarah = dictionaryNew();
// Set tricia's properties.
tricia
.set( "Name", "Tricia" )
.set( "Age", 38 )
.set( "Attitude", "Positive" )
;
// Set sarah's properites.
sarah
.set( "Name", "Sarah" )
.set( "Age", 32 )
.set( "Attitude", "Bubbly" )
;
// Output the two name values out.
writeOutput( "Name: #tricia.get( 'Name' )#" );
writeOutput( "<br />" );
writeOutput( "Name: #sarah.get( 'Name' )#" );
</cfscript>
Here, the dictionaryNew() function defines four local values:
- cache
- dictionary
- dictionary.get
- dictionary.set
Because the .get() and .set() methods are defined alongside the "cache" and "dictionary" values, the .get() and .set() closures can make reference to the cache and dictionary values. In this way, the .get() and .set() methods provide public access to the private cache of values.
Notice that we are creating two different instances of our "dictionary" with different values. Each of these dictionary gets its own local cache and get()/set() closures. When we run the above code, we get the following output:
Name: Tricia
Name: Sarah
We've leveraged the local function scope (of dictionaryNew()) and the power of closures to create component-like functionality. In JavaScript, you see this kind of programming with the "Module Pattern" or the "Revealing Module Pattern."
This is getting kind of interesting, No?
Folding Collections - Or, Functional Programming Is Fun!
I don't know how to define "Functional Programming" exactly; but, in functional programming, Functions are the main players, not objects. I believe that typically, with functional programming, objects are seen more as "static" values. And, rather than mutating them, functions are used to transform one static object into other static object.
That's probably inaccurate and / or an over simplification of what functional programming is; but, I do know that when I learn about functional programming, I often learn about functions like foldl() and foldr() that are used to gather aggregate, calculated values across an entire collection.
I learned about this kind of programming when I was reading Seven Languages In Seven Weeks by Bruce Tate. And, in fact, I tried to play around with the idea in ColdFusion, using a CFML pre-processor. However, with the introduction of function expressions in ColdFusion 10, this kind of functional programming (folding, mapping, filtering, etc.) is now much more readily available.
In the following demo, we're creating a higher-order function, foldl(), which aggregates a calculated value over every index of the given collection.
<!---
Start a CFScript block. Closures can only be used inside
of CFScript due to the syntax required to define them.
--->
<cfscript>
// I take a collection and fold each value into the next using
// the operator (callback) on each value. The result is an
// aggregated value based on each item in the collection.
function foldl( collection, initialValue, operator ){
// Start out with the initial value as the one we pass
// into the first callback.
var aggregateValue = initialValue;
// Iterate over the collection in order to folder the
// aggregate across all the values of the collection.
arrayEach(
collection,
function( currentValue ){
// Update the aggregate.
aggregateValue = operator(
aggregateValue,
currentValue
);
}
);
// Return the folded, aggregate value.
return( aggregateValue );
}
// ------------------------------------------------------ //
// ------------------------------------------------------ //
// Create a collection of number.
values = [ 1, 2, 3, 4, 5, 6 ];
// Determine the multiplication product of the entire array
// by folding the product across each value in the array.
result = foldl(
values,
1,
function( product, value ){
return( product * value );
}
);
// Output the resultant product.
writeOutput( "Product: " & result );
</cfscript>
As you can see, we have an array of numbers and we are using the foldl() function to calculate the combined product of all of those numbers. When we run the above code, we get the following output:
Product: 720
Functional programming is really pretty cool. If you use JavaScript and jQuery, you probably already use it all the time with functions like each() and filter() and map(). Now, function expressions and closures have unlocked the joy of Functional programming in ColdFusion 10; we could always pass functions into other functions - but now, it's actually enjoyable.
Creating Self-Aware Functions
The system goes on-line August 4th, 1997. Human decisions are removed from strategic defense. Skynet begins to learn at a geometric rate. It becomes self-aware at 2:14 a.m. Eastern time, August 29th.
3 billion human lives ended on August 29th, 1997. The survivors of the nuclear fire called the war Judgment Day. They lived only to face a new nightmare: the war against the machines. The computer which controlled the machines, Skynet, sent two Terminators back through time. Their mission: to destroy the leader of the human resistance, John Connor, my son. The first Terminator was programmed to strike at me in the year 1984, before John was born. It failed. The second was set to strike at John himself when he was still a child. As before, the resistance was able to send a lone warrior, a protector for John. It was just a question of which one of them would reach him first. - Sarah Connor, Terminator 2: Judgement Day.
Closures allow us to create functions that remain continuously aware of other variables defined in their lexical scope chains. This means that we can define functions that know about themselves. If we define a named variable and immediately assign a function to it (Function Expression), the function is aware of its own name (and reference). And, if it knows how to reference itself, it knows how to access its own meta data.
Once a function becomes aware of itself, it can start to introspect itself. In this demo, we'll create a function that can examine its own meta data, looking for a specific property.
<!---
Start a CFScript block. Closures can only be used inside
of CFScript due to the syntax required to define them.
--->
<cfscript>
// I am a factory that creates a function that is aware of its
// own meta data.
function getSelfAwareFunction(){
// Define the function as a local, named variable. This way,
// the function body will have access to a named reference
// back to itself.
var me = function(){
// Get the meta data for "myself.
var metaData = getMetaData( me );
// Check to see if the a Cyberdyne name has been applied.
if (structKeyExists( metaData, "cyberdyneName" )){
// Return a self-aware value.
return( metaData.cyberdyneName & " is now self-aware." );
} else {
// Not ready for self-aware life.
return( "Please define my name." );
}
};
// Return the self-aware function.
return( me );
}
// ------------------------------------------------------ //
// ------------------------------------------------------ //
// Get the self-aware function.
isSelfAware = getSelfAwareFunction();
// Check to see if the method is self-aware.
writeOutput( isSelfAware() & "<br />" );
// Get the meta-data of the function.
metaData = getMetaData( isSelfAware );
// Assign a name to the META data.
metaData.cyberdyneName = "Skynet";
// Try checking the self-aware nature of the method again.
writeOutput( isSelfAware() & "<br />" );
// ------------------------------------------------------ //
// ------------------------------------------------------ //
// Clean up meta-data for demo.
structDelete( getMetaData( isSelfAware ), "cyberdyneName" );
</cfscript>
In this demo, we're creating a function and assigning it to a locally-scoped variable, "me." Since this variable and the function were both defined in the same local scope, they remain forever bound. This means that the function can always access itself using the "me" variable; and, through this variable reference, it can access its own meta data. In this case, it [the function] looks for a meta property, "cyberdyneName." When we run the above code, we get the following output:
Please define my name.
Skynet is now self-aware.
As we saw before, with the "module pattern," closures remain bound to the other variables defined in the same context. As far as the rest of the world is concerned, these variables are completely hidden. Meta data on the function itself, however, can be accessed both by the external world as well as by the function itself - this allows for an interesting cross-section of access and communication.
Creating Variable Sandboxes
In JavaScript, a very popular technique is to use an anonymous functions to create a sandboxe in which variables can be created without polluting the "global scope." While the same kind of syntax (immediately-invoked function expressions / self-executing functions) is not yet available in ColdFusion 10 (a parsing concern), we can still use function expressions and higher-order functions to mimic the same idea.
When we execute a function, the function obtains a private, local scope. This approach uses that local scope as a way to define functionality without adding to the page-level Variables scope. Since we can't immediately invoke an anonymous function in ColdFusion 10 beta, we'll create a function - runOnce() - which will handle the execution for us:
<!---
Start a CFScript block. Closures can only be used inside
of CFScript due to the syntax required to define them.
--->
<cfscript>
// I take the given callback and execute it once, passing in
// the subsequent arguments as invocation parameters.
function runOnce( /* [ args ,]*/ ){
// We'll need to build up a collection of offset arguments.
var callback = arguments[ arrayLen( arguments ) ];
var callbackArguments = {};
// Map the incoming arguments (N-1) onto the callback
// arguments (N).
for (var i = 1 ; i < arrayLen( arguments ) ; i++ ){
callbackArguments[ i ] = arguments[ i ];
}
// Invoke the callback with the arguments.
callback( argumentCollection = callbackArguments );
}
// ------------------------------------------------------ //
// ------------------------------------------------------ //
runOnce(
"Sarah",
"You are looking quite amazing tonight!",
function( name, compliment ){
writeOutput( name & ", " & compliment );
}
);
// ******
// NOTE: HUGE bug that prevents function expression from
// being the FIRST argument to a function. FILE THIS BUG.
// ******
</cfscript>
Here, the runOnce() function takes any number of arguments in which the last one is the closure. The runOnce() function then invokes the closure function, passing in the [1..N-1] arguments to it. In this way, the closure acts as a sandbox, protecting the inner content from the rest of the Variables scope (or rather, the other way around). When we run the above code, we get the following output:
Sarah, You are looking quite amazing tonight!
As you can see, the two string values were translated onto the arguments, Name and compliment, respectively. Not only did this keep the "name" and "compliment" variables out of the Variables scope, it completely encapsulated their used within the closure.
NOTE: I stumbled across a huge bug in this last demo which I'll blog about shortly.
Hopefully, you're beginning to get an idea of what closures are and what kind of functionality they can provide. My goal with this post wasn't to talk about Why we should use them - as I said before, I think that question will be a journey that the ColdFusion community goes on together; this post was supposed to be simply technical in nature.
For now, I've just looked at closures in an isolated way. I am sure that things will get even more interesting when we start to examine closures in the context of Threads and Components. More to come on that topic!
Want to use code from this post? Check out the license.
Reader Comments
Great stuff Ben. Closures are pretty mind bending, thanks for writing this up. Timely as I am just getting into closures via javascript.
One thought occurs to me - is there no native queryEach(query, function) ? Seems a no-brainer, CF query objects are pretty common. I guess Adobe left it as an exercise for you to complete ;)
@Darren,
Yeah, Closures are pretty awesome! As far as the queryEach(), the CFScript tag now supports for-in behavior for queries (though I haven't tried it yet -- just saw it in the What's New doc). So, you should be able to do:
But, you can do the same with arrays also AND they still have an arrayEach() functions - so clearly, for-in is not mutually exclusive with each-style iterators. Good catch!
Great examples! I especially like the 'light-weight objects' example since creating component objects can be quite slow in Coldfusion.
Is it possible to use these light-weight objects with remote services for flex? Is adding a '__TYPE__' key to var cache enough for flex to know the type of the object?
@Ben - ok, can you answer me this.
As I see it, looping through CF query objects is as old as time itself. But one of the classic issues is that combining row data with aggregate data can sometimes force you to loop through a query more than once.
Query of Queries is one way to solve this, I tend to create complex structures on an initial loop through.
Are closures an elegant solution to this? In your example above, when returning a random greeting, can you code it so that the same greeting is not used more than once?
@Jan,
Oh, I don't know enough about Flex to even being to answer that. My guess it that you need a ColdFusion component for Flex integration because there is probably a lot more going on behind the scenes for data translation??? Of course, that is pure speculation.
Glad you like the examples! Hopefully I'll have some more Closure-oriented stuff in the next few days.
Nice presentation, Ben. My background includes a whole bunch of languages and I assume that closures are a natural part of most new breeds.
I seem to be seeing more and more interfaces to the pure Java libraries and wonder if it wouldn't make sense for CF to provide cleaner and safer mechanisms than the CreateObject() and JavaCast() functions? Whenever I see one of these, I feel like I used to when calling some 3rd party DLL function - not sure what was going to happen next.
Since I am such a n00bie, it seems hard to know when to branch out into the lower(/higher?) level classes that are available via Java. Is there a nice guideline about what to use in different cases (CF vs JVM)?
Longest Blog post ever.
Very worthwhile, I'll need to come back and watch the videos. Not to mention the external links.
Thanks.
@Ben,
It's possible to pass a for example a struct to flex as a typed object if this struct has a key named '__TYPE__' (like myStructObj['__TYPE__'] = 'path.to.component'). Documentation:
http://livedocs.adobe.com/coldfusion/8/htmldocs/help.html?content=UseFlexDataService_05.html
This increased performance when a large number of objects needed to be passed to flex. The downside is that you no longer had methods in the objects since they were structs.
That's no longer the case with your dictionary example.
But i'm drifting a bit off topic here. I will give this a try though.
Hi there, I present the iteration protocol:
https://gist.github.com/2157881
From a pythonist with love ;D
@Ben (the Good one)
How are Function Expressions not the same thing as cfmodule template includes? You know how you could, in the past, simply include a good amount of code with a simple tag? I think I have the tag wrong. But you could say
Hello <cfmodule="printName(#username#)> how is your <cfinclude="dayPart(#currentTime#)>? You would typically do this for larger pieces of the page.
Either the world of web development is throwing another monkey wrench my way, or you mixed up some semantics in this blog post. In particular, "Function Declaration" isn't defined correctly. Coldfusion has always supported definitions, and it has supported declarations at least as long as it supported the cfinterface tag. According to the Adobe documentation on interfaces,
The cfinterface tag declares a set of related functions that any ColdFusion component (CFC) that implements the interface must define. The interface specifies function signatures, but does not implement the functions; instead, the CFC that implements the interface must contain the full function definitions.
Meaning that a declaration is how you communicate with the function, but not the implementation of the function, and the definition is what it does internally. Also, what you refer to as a "Function Expression" sounds a lot like a delegate, which incidentally, is an expression when invoked, or it can be used as part of a larger expression.
ArraySort() using closure example, fyi
<code>
data = [ "d", "b", "a", "c" ];
arraySort( data, function( a, b ) {
return compare( a, b );
});
// data is now: [ "a", "b", "c", "d" ]
<code>
Ben, Great post and very informative. This article has been rolling around my "to read" list for awhile but was so long that I didn't get to it until now.
I'm glad to see that the CF team is continuing to add new functionality around popular concepts.
One thought I had on the REReplaceAll/REExtractAll functions was that maybe having a forEachMatch function that both of these higher-order functions call. This way REExtractAll wouldn't have to depend on a side-effect of REReplaceAll. Just a thought.
Thanks again for a great article!
Wonderful Ben, thanks for the code! Would you please consider posting these codes, especially reReplaceAll to CFLIB.org?