Skip to main content
Ben Nadel at cf.Objective() 2013 (Bloomington, MN) with: Reem Jaghlit
Ben Nadel at cf.Objective() 2013 (Bloomington, MN) with: Reem Jaghlit

Cycling Through A Sequence In ColdFusion

By
Published in

Yesterday, I was working on a sortable datagrid with server-side pagination; and I was writing logic to toggle between ASC and DESC directions based on the user's current selection. It occurred to me that a more generic perspective on this would be to take a "value" and then "cycle" it through a sequence of values. As a fun Friday code kata, I wanted to try this in ColdFusion.

Consider the sequence of values:

[ "A", "B", "C", "D" ]

For a given value — C — I might want to get the next value (D) or the previous value (B). If we think of the collection as a "sequence", we can use the common ColdFusion naming convention to envision two built-in functions (BIFs):

  • sequenceNext( sequence, current ) - locates the current value; then returns the next element by index. If the next index goes beyond the end of the array, the first element is returned (cycling back to the head of the sequence).

  • sequencePrevious( sequence, current ) - locates the current value; then returns the previous element by index. If the previous index goes beyond the head of the array, the last element is returned (cycling back to the end of the sequence).

The native ColdFusion BIFs often allow an argument to be either a value or a closure. But I like to separate those two concerns into different function signatures. Which means that we can create two more BIFs that take a function as the second argument:

  • sequenceNextWith( sequence, operator )
  • sequencePreviousWith( sequence, operator )

I like the suffix *With() to indicate that we're going to be performing the action with the help of the passed-in operator. I could have done this with a type-check internally to a single method; but, I'd like to have 4 methods with crystal clear responsibilities rather than 2 methods with fuzzier responsibilities. Your mileage may vary.

Plus, the nice thing about this is that we can easily fulfill the value-based comparison implementation with a one-line transformation that takes the static argument — current — and passes-it-on to the closure-based BIF quite easily:

( value ) => ( value == current )

In this case, the value is the iteration value of the sequence being compared to the passed-in current argument.

With that said, here's my implementation of these four ColdFusion functions. I'm going to cycle through a sequence of letter and output the prev/next values per element:

<cfscript>

	// ColdFusion language extensions (global functions).
	include "/core/cfmlx.cfm";

	// ------------------------------------------------------------------------------- //
	// ------------------------------------------------------------------------------- //

	sequence = [ "A", "B", "C", "D", "E" ];
	current = "A";

	// Start with "A", then cycle the current element through the sequence 10-times. This
	// is enough to demonstrate the wrapping around the limits of the array.
	for ( i = 1 ; i <= 10 ; i++ ) {

		next = sequenceNext( sequence, current );
		previous = sequencePrevious( sequence, current );

		echo( previous );
		echo( " &larr; <mark>[ #current# ]</mark> &rarr; " );
		echo( next );
		echo( "<br />" );

		current = next;

	}

	// ------------------------------------------------------------------------------- //
	// ------------------------------------------------------------------------------- //

	/**
	* I cycle to the next element in the sequence after the current (given) value. The
	* location of the current value is determined by walking from the head of the array
	* and performing a simple quality check. If the current value is located in the last
	* index, the cycle wraps around to the first index.
	*/
	public any function sequenceNext(
		required array sequence,
		required any current
		) {

		return sequenceNextWith( sequence, ( value ) => ( value == current ) );

	}

	/**
	* I cycle to the next element in the sequence after the matching predicate. The
	* location of the matching predicate is determined by walking from the head of the
	* array and performing an invocation against the iteration value. If the matching
	* iteration value is located in the last index, the cycle wraps around to the first
	* index.
	*/
	public any function sequenceNextWith(
		required array sequence,
		required function operator // Predicate (value) -> Boolean
		) {

		cfloop(
			array = sequence,
			index = "local.i",
			item = "local.value"
			) {

			if ( operator( value ) ) {

				// Caution: NULL coalescing assumes that none of the sequence elements
				// will be NULL, which I thin is a fair assumption.
				return ( sequence[ i + 1 ] ?? sequence[ 1 ] );

			}

		}

		throw(
			type = "CurrentNotFound",
			message = "Current value not found in sequence."
		);

	}

	/**
	* I cycle to the previous element in the sequence before the current (given) value.
	* The location of the current value is determined by walking from the head of the
	* array and performing a simple quality check. If the current value is located in the
	* first index, the cycle wraps around to the last index.
	*/
	public any function sequencePrevious(
		required array sequence,
		required any current
		) {

		return sequencePreviousWith( sequence, ( value ) => ( value == current ) );

	}

	/**
	* I cycle to the previous element in the sequence before the matching predicate. The
	* location of the matching predicate is determined by walking from the head of the
	* array and performing an invocation against the iteration value. If the matching
	* iteration value is located in the first index, the cycle wraps around to the last
	* index.
	*/
	public any function sequencePreviousWith(
		required array sequence,
		required function operator // Predicate (value) -> Boolean
		) {

		cfloop(
			array = sequence,
			index = "local.i",
			item = "local.value"
			) {

			if ( operator( value ) ) {

				// Caution: NULL coalescing assumes that none of the sequence elements
				// will be NULL, which I thin is a fair assumption.
				return ( sequence[ i - 1 ] ?? sequence[ -1 ] );

			}

		}

		throw(
			type = "CurrentNotFound",
			message = "Current value not found in sequence."
		);

	}

</cfscript>

Note that I'm using the NULL coalescing operator (??) to implement the wrap-around behavior of the sequence cycling. This implies that none of the sequence values will ever be NULL; which I think is a fair assumption for this type of work.

If we run this Adobe ColdFusion 2025 code, we get the following output:

I honestly don't know how much this kind of functionality would come up in my ColdFusion programming. But that's the fun of the code kata — it just gets you to think about the code, it doesn't have to result in anything immediately useful.

Want to use code from this post? Check out the license.

Reader Comments

Post A Comment — I'd Love To Hear From You!

Post a Comment

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel
Managed ColdFusion hosting services provided by:
xByte Cloud Logo