Skip to content

Instantly share code, notes, and snippets.

@bennadel
Created February 20, 2012 14:52
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save bennadel/1869544 to your computer and use it in GitHub Desktop.
Save bennadel/1869544 to your computer and use it in GitHub Desktop.
ColdFusion 10 Beta - Closures And Function Expressions - Part I
<!---
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>
<!---
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>
<!---
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>
<!---
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>
<!---
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>
<!---
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>
<!---
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment