CFML wishlist: All collections should extend Iterator

Have you ever really given a second thought to the fact that in ColdFusion/CFML you have to loop queries, arrays, and structures in completely different ways?   For example, in each of these things, we are essentially doing the same thing:

<!--- looping our query --->
<cfloop query="myQuery">
	<cfset doStuff() />
</cfloop>
<!--- looping our array --->
<cfloop array="#myArray#" index="i">
	<cfset doStuff() />
</cfloop>
<!--- or --->
<cfloop from="1" to="#ArrayLen(myArray)#" index="i">
	<cfset doStuff() />
</cfloop>

<!--- looping our structure --->
<cfloop collection="#myStruct#" item="i">
	<cfset doStuff() />
</cfloop>

In each of these cases, we are essentially doing the same thing, that being looping a collection that contains multiple items and acting on each iteration.  I have always liked the fact that the ColdFusion array can be converted to a Java iterator like this:

<cfset iterator = myArray.iterator() />
<cfloop condition="#iterator.hasNext()#">
	<cfset thisIteration = iterator.next() />
	<cfset doStuff() />
</cfloop>

However, given the fact that <cfloop array=”#myArray#” index=”i”> is already an abstraction, it doesn’t make sense to use this in most cases.   But wouldn’t it be cool if you could call myQuery.iterator() or myStruct.iterator() and have the same functionality?  Or even better, why not have those collections all extend an iterator class so that it could be simplified even futher with myQuery.hasNext() or myStruct.hasNext().

Keep in mind, this discussion is only coming from the perspective of the programming interface itself, and I am not going to get into the differences behind the scenes of how a query result is actually a set of arrays of columns, or how an array differs from a struct.  My point is simply that if we have these abstractions, it sure would be cool if they were consistent, but we were still able to call specific functions on them like ArrayFind() , StructDelete(), etc.  If we had this ability, our loops in CFSCRIPT would be a lot more consistent as well.

With that said…. I will leave you with my wishful implementation for writing loops in CFML:

<cfloop condition="#myQuery.hasNext()#">
	<cfset thisRow = myQuery.next() />
	<cfset doStuff() />
</cfloop>

<cfloop condition="#myArray.hasNext()#">
	<cfset thisItem = myArray.next() />
	<cfset doStuff() />
</cfloop>

<cfloop condition="#myStruct.hasNext()#">
	<cfset thisItem = myStruct.next() />
	<cfset doStuff() />
</cfloop>

Converting ColdFusion Arrays to Java Iterators

I have been working with Reactor for several months now and have grown fond of using iterators as collections of children objects.  I never looked at the voodoo magic under the covers that makes that happen in the Reactor core files, but with a little playing around and testing today I realized it might not be so magic afterall.  I created a CFC that accepted an array as an argument, then returned that array’s iterator() method.   It is plainly obvious by looking at the code, but I just never knew you could do this.  For anyone interested, here is the source of my test:

IteratorTest.cfc

<cfcomponent name=”IteratorTest” hint=”I test iterator stuff”>
<cffunction name=”init”>
<cfreturn this />
</cffunction>

<cffunction name=”returnIterator” returntype=”any”>
<cfargument name=”MyArray” type=”array” required=”true” />
<cfreturn arguments.MyArray.iterator() />
</cffunction>
</cfcomponent>

IteratorTest.cfm
<cfscript>
IteratorTest = CreateObject(“component”,”IteratorTest”).init();
MyArray = ArrayNew(1);
MyArray[1] = “one”;
MyArray[2] = “two”;
MyArray[3] = “three”;
</cfscript>

<cfdump var=#MyArray# />

<cfset MyIterator = IteratorTest.returnIterator(MyArray) />
<cfdump var=#MyIterator# />

<cfloop condition=#MyIterator.hasNext()#>
<cfset thisItem = MyIterator.next() />
<cfoutput>#thisItem#<br /></cfoutput>
</cfloop>

EDIT: useful comment from Mark Mandel on the matter:

I like this way of looping around arrays, but you can also do the same thing with Structs, but it just takes a little bit more work:

iterator = myStruct.values().iterator();

It should be noted that iterators for both Structs (Hashtables) and Arrays (Vectors) are ‘fail-fast’ – which means if someone removes and object from the array/struct not through that iterator, an exception will be thrown.

It may be worth making a top level copy of the array before returning the iterator to avoid collisions like these.