Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
I am the chief technical officer at InVision App, Inc - a prototyping and collaboration platform for designers, built by designers. I also rock out in JavaScript and ColdFusion 24x7.
Meanwhile on Twitter
Loading latest tweet...
Ben Nadel at cf.Objective() 2009 (Minneapolis, MN) with:

ColdFusion ArraySplice() Method As An Example Of A Dynamic Method Signature

By Ben Nadel on
Tags: ColdFusion

Last week, I blogged about using ColdFusion's CFParam tag as a possible way to enforce CFArgument-like functionality in a situation where you might not know which arguments will exist at compile time. That post spun off into a very interesting conversation about the pros and cons of using named-arguments versus ordered-arguments and when each makes sense (or should be required).

I think the biggest factor that directed that conversation was the fact that in my example method, optional arguments could precede other optional arguments, depending on the number of arguments being passed-in. In a language like ColdFusion where different method signatures don't necessitate different physical functions, there is definitely a funky feeling to that. As such, I wanted to come up with a better example method in which the optional arguments were always at the end of the arguments list.

While this doesn't relate at all back to the concept of using CFParam to define dynamic arguments, I thought porting over a Splice() method (from Javascript) to ColdFusion, was a great example of a highly dynamic method signature. If you are not familiar with Javascript's Array::Splice() method, it can both delete 0..N elements and insert 0..N elements from and to an array at the same time.

  • <cffunction
  • name="arraySplice"
  • access="public"
  • returntype="array"
  • output="false"
  • hint="I splice an array by deleting and / or adding new elements at the given index.">
  •  
  • <!--- Define arguments. --->
  • <cfargument
  • name="array"
  • type="array"
  • required="true"
  • hint="I am the array being manipulated."
  • />
  •  
  • <cfargument
  • name="index"
  • type="numeric"
  • required="true"
  • hint="I am the index at which to start adding or removing elements to and from the array (respectively)."
  • />
  •  
  • <cfargument
  • name="howMany"
  • type="numeric"
  • required="true"
  • hint="I am the number of elements that should be removed from the array, starting at the given index. If zero, no elements will be removed."
  • />
  •  
  • <!---
  • At this point, the method can take a variable number of
  • subsequent arguments that would be the elements to insert
  • into the array at the given index.
  •  
  • <cfargument name="element1" />
  • <cfargument name="element2" />
  • <cfargument name="element3" />
  • ....
  • <cfargument name="elementN" />
  • --->
  •  
  • <!--- Define the local scope. --->
  • <cfset var local = {} />
  •  
  • <!---
  • The first thing we want to do is delete any elements from
  • the array that we need to. Since ColdFusion doesn't have
  • an inherent way to delete more than one item, we will have
  • to perform this as a conditional loop.
  • --->
  • <cfset local.deleteCounter = arguments.howMany />
  •  
  • <!---
  • Keep deleting while the DeleteCounter is still non-zero
  • AND while the array length is big enough to encompass the
  • given index.
  • --->
  • <cfloop condition="(local.deleteCounter-- && (arrayLen( arguments.array ) gte arguments.index))">
  •  
  • <!--- Delete the given item in the array. --->
  • <cfset arrayDeleteAt(
  • arguments.array,
  • arguments.index
  • ) />
  •  
  • </cfloop>
  •  
  • <!---
  • Now that we have delete any necessary elements from the
  • array, we need to add any additional items, starting at
  • the given instance.
  • --->
  •  
  • <!---
  • Since we will need to keep updating the index of the
  • insert, start a new pointer for that index.
  • --->
  • <cfset local.insertIndex = arguments.index />
  •  
  • <!---
  • Loop over the optional elements in the arguments. There
  • may be ZERO or more of these elements.
  • --->
  • <cfloop
  • index="local.elementIndex"
  • from="4"
  • to="#arrayLen( arguments )#"
  • step="1">
  •  
  • <!---
  • When inserting, we need to make sure that the index
  • of insertion is not beyond the bounds of the array.
  • If it is, we need to do an append (or the insert
  • will error).
  • --->
  • <cfif (local.insertIndex gt arrayLen( arguments.array ))>
  •  
  • <!--- Append the new element. --->
  • <cfset arrayAppend(
  • arguments.array,
  • arguments[ local.elementIndex ]
  • ) />
  •  
  • <cfelse>
  •  
  • <!--- Insert the new element. --->
  • <cfset arrayInsertAt(
  • arguments.array,
  • local.insertIndex,
  • arguments[ local.elementIndex ]
  • ) />
  •  
  • </cfif>
  •  
  • <!--- Increment insertion index. --->
  • <cfset ++local.insertIndex />
  •  
  • </cfloop>
  •  
  • <!--- Return the updated array. --->
  • <cfreturn arguments.array />
  • </cffunction>

As you can see, the first three arguments to the ArraySplice() method are always static; but then, you can append as many additional arguments to the method call as you want for the insert functionality. To see this in action, take a look at the following demo (I have created comments for the intermediary results):

  • <!--- Create an array of test values. --->
  • <cfset values = [ 1, 2, 3 ] />
  •  
  • <!--- Delete the last two values. --->
  • <cfset values = arraySplice( values, 2, 2 ) />
  • <!--- // [ 1 ]. --->
  •  
  • <!--- Insert two values at the end. --->
  • <cfset values = arraySplice( values, 2, 0, "A", "B" ) />
  • <!--- // [ 1, "A", "B" ]. --->
  •  
  • <!--- Insert two values at the beginning. --->
  • <cfset values = arraySplice( values, 1, 0, "Z", "Y" ) />
  • <!--- // [ "Z", "Y", 1, "A", "B" ]. --->
  •  
  • <!---
  • Both delete the middle value and insert two more. Notice
  • that delete and insert use the same index value.
  • --->
  • <cfset values = arraySplice( values, 3, 1, "*", "*" ) />
  • <!--- // [ "Z", "Y", "*", "*", "A", "B" ]. --->
  •  
  •  
  • <!--- Output the array (as a list). --->
  • <cfoutput>
  • #arrayToList( values, ", " )#
  • </cfoutput>

When we run this combination of deletes and inserts, we get the following array-to-list output:

Z, Y, *, *, A, B

Nothing too technically savvy going on here, other than the CFLoop condition that both sets and tests a value (sweeet!); mostly, I thought the ArraySplice() method was just an elegant example of a method signature that could take a variable number of arguments.




Reader Comments

Would using the built in java functions be faster on large arrays?

exp:

<cfset var myArray = createObject("java", "java.util.ArrayList").Init(arguments.array) />

...

<cfreturn myArray.subList(JavaCast("int", arguments.index - 1), JavaCast("int", howMany)) />

Reply to this Comment

@Christopher,

I think using the underlying Java stuff is definitely exciting and often times adds efficiencies. I just worry sometimes about using undocumented stuff; I used to be very pro it, but people have made me wear of it.

Perhaps it's time to rebuild my courage :)

Reply to this Comment

Nice to see someone giving CF a little "dynamic method signature" discussion love.

Intelligently overloaded function signatures are one of the most powerful techniques for quality API authoring. jQuery's selector method is probably the greatest single example of how you can consolidate an entire spectrum of implementation and functionality into a single, pristine entry point. A lot of the CFML extension utilities I use behave differently depending on the method signature. One side effect I've noticed is that the more flexible and multifunctional the API functions you author are, ironically, the easier it becomes to remember those variant signatures and what they do. Great post.

Reply to this Comment

@David,

Glad you like. It is some really cool functionality, but one that I have not had the best chance to use yet (or at least not that I have thought of).

Reply to this Comment

Java is awesome and I love seeing new twists on it.

@Ben - I totally agree with you.

Reply to this Comment

@ Ben-- Re: inserting and testing insert position--why not iterate the insert values backward and then reuse the same insert position? (Yes, I realize this is years after your post, but I think this is an... evergreen(?) response.)

Reply to this Comment

@Neil,

Years later... but, that's actually a pretty clever idea. If you iterate backwards, you're totally right, less calculation in the insertion, but the same overhead (as far as I can see). Good thinking!

Reply to this Comment

Post A Comment

You — Get Out Of My Dreams, Get Into My Comments
Live in the Now
Oops!
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.