Calling ColdFusion Function Literals Like You Do In Javascript

One of the greatest things in Javascript is the use of the "headless" or "anonymous" function. You can define a function on the fly and pass it in as a method argument:

// Replace a value in a text field.

strText.replace(

new RegExp( "[0-9]", "g" ),

function( $0 ){

return( "blam" )

}

);

This Javascript replace method takes a headless function and uses it to evaluate each replace event.

After reading Sean Corfield's Closure Example I was inspired to mess with variable binding and function literals. To be honest, I still don't quite get closures. I think I have some mental block. maybe it's the "call()" method. What's that call() method all about? Can't you just execute a method directly? Clearly I am missing something.

So anyway, I went ahead and tried to create something like the headless Javascript method (above) but in ColdFusion. My example here takes text, a regular expression for phone numbers, and changes their format based on the function literal:

<!--- Store some text. --->

<cfsavecontent variable="strText">

For a good time, give Cindy a call at

212-555-1245. But, if you are feeling especially

naughty, try calling Betty at 555.5534.

</cfsavecontent>

<!---

Replace the phone number formatting using

our passed in function.

--->

<cfset strNewText = REReplaceWithMethod(

Text = strText.Trim(),

RegEx = "(?:(\d{3})[ .-])?(\d{3})[ .-](\d{4})",

Method =

"function( $0, $1, $2, $3 ){

if (Len( $1 )){

return( '(##$1##) ##$2##-##$3##' );

} else {

return( '##$2##-##$3##' );

}

}"

) />

As you can see, I am searching for an optional three digits followed by 3 and 4 more digits (with various delimiters). Notice that the because of the optional leading group, I have to have an IF statement in my passed in method. The function must be passed in as a string and must have named arguments. Running that code gives me:

For a good time, give Cindy a call at (212) 555-1245. But, if you are feeling especially naughty, try calling Betty at 555-5534.

It worked perfectly! Ok, so here's how it is done:

<cffunction

name="REReplaceWithMethod"

access="public"

returntype="string"

output="false"

hint="This takes a string, a regular expression, and a method against which each matching will be applied for the given replace.">

<!--- Define arguments. --->

<cfargument

name="Text"

type="string"

required="true"

hint="This is the text value we are going to manipulate."

/>

<cfargument

name="RegEx"

type="string"

required="true"

hint="This is our JAVA regular exiression."

/>

<cfargument

name="Method"

type="string"

required="true"

hint="This is our function string literal that we will we use to evaluate each match."

Take a look at the variable binding (the stuff inside of the CFInvoke). This was the trickiest part. I am sure that this can be done in a much cleaner way, but I am at a loss as to how to do it. Anyway, I thought this was kind of a cool experiment. I wish this stuff was as easy and awesome as it was in Javascript, but I understand that due to compiling nature of ColdFusion, it just cannot be.

Reader Comments

Part of the problem here is that you are sort of confusing two levels of binding. There's the basic variable binding and then there's the pattern matching binding (of $n to parts of the matched string). That makes the problem doubly complicated.

With Closures for CFMX, you can certainly have anonymous arguments - you access them positionally in the closure code using arguments[n].

However, the closest idiomatic CF usage to what you show would be to have a two-argument closure that you pass the text string and the array result of the REFind() call into:

You have to explicitly "call" the closure because it is not just a function, it is an object that has bound variables. Again, your example has no bound variables so you're not leveraging the power of closures.

Remember: a closure is not "just" an anonymous function, in the same way that in Java, an anonymous inner class is not synonymous with a closure either.

Thanks for taking the time to respond. I think I realize now that I know even less about closures than I realized I did :) I sort of see what you are saying, but I think I have to really go pick apart your Closure code to try and understand better.

I understand being able to access variable length arguments via ARGUMENTS[ n ]. But what I cannot figure out is how to invoke a method and pass a variable number of arguments.

I thought maybe I could build an ArgumentCollection object as an array, but I don't think it was happy with that. I can't use CFInvoke since that requires name/value pairs. Do you have any suggestions?

@Sean:Can you offer a practical example of when a closure might be preferable to a more "traditional" approach? I'm with Ben, I think, in that closures tend to baffle me a bit, but maybe that's because I can't wrap my mind around a practical application for them. I also read your post this morning, but it didn't help me much (the darkness is just that thick).

Those are all valid calls, it's all a matter of what args you want to pass (maybe I'm not understanding what you're asking).

You could of course use cfinvoke with closures (method="call") to loop over cfinvokeargument tags.

@Rob, I'll post a few more examples on my blog in due course. Hopefully I can find something simple enough that folks can follow but meaty enough that folks see why a "traditional" approach would be much more work.

Not sure what you ultimately did to solve this, but I thought of few things.

First, it seems a little odd that you would want a function that can take a variable number of arguments and not know the names of the arguments. Though obviously this being coldfusion overriding functions is done in the non-normal way of isdefined() within the function. It still seems odd to me, rather than having a single array variable which can have one or more elements.

However.

Arguments sent to a cffunction are ordered in arguments 1 through etc. You can do a structKeyArray( arguments ) and then do arrayLen to get length of arguments and use arguments[ num ] to grab the value. If the value happens to be a struct you can then have your own custom names for keys.