Created
February 20, 2012 14:52
-
-
Save bennadel/1869544 to your computer and use it in GitHub Desktop.
ColdFusion 10 Beta - Closures And Function Expressions - Part I
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!--- | |
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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!--- | |
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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!--- | |
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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!--- | |
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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!--- | |
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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!--- | |
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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!--- | |
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