Skip to main content
Ben Nadel at InVision In Real Life (IRL) 2018 (Hollywood, CA) with: Shawn Grigson
Ben Nadel at InVision In Real Life (IRL) 2018 (Hollywood, CA) with: Shawn Grigson

Using Labeled Loops In ColdFusion

By
Published in Comments (6)

The other day, I was watching a tutorial on the Svelte JavaScript framework and I saw that they used the $: notation to denote reactive values. I believe that this is just an old JavaScript feature for labeling parts of the code (which Svelte is then overloading). I've never used that JavaScript feature myself; but, it did remind me that some of this labeling concept is also available in ColdFusion as well, specifically for loops. I've never used this feature in ColdFusion either; so, I thought it might be worth a quick exploration.

I couldn't find much on how this feature actually works outside of this article on the ColdFusion blog. The idea is that you can add a label to either a for or a while loop; and then, consume that label in either a break or a continue statement. Essentially, you can tell ColdFusion which loop you are referencing in your break / continue statements.

Which, is really only meaningful if you have nested loops. Otherwise, the break and continue statements simply reference the one contextual loop that you are in.

Part of me feels that labeling loops falls under the "too clever" umbrella of programming approaches; and, is likely to make the code harder to read and maintain. But, if encapsulated behind some meaningful abstraction, it could be OK to use from time to time.

And, to be honest, I had a hard time thinking of a real-world scenario in which I might want to have nested loops with label-based control flow. What I eventually came up with was a fuzzy-matching algorithm for text search. Given a target value and a search value, we want to determine if ever letter within the search value can be located - in order - within the target value, even if not within a contiguous string of characters.

For this, I'm going to have two nested loops:

  • Looping over the search characters.
  • Looping over the target characters.

As I compare one character to another, I'm going to have opportunities to both break and continue from the inner loop to the outer loop. This is hard to explain in words, so let's take a look at the ColdFusion code (which works in both Adobe ColdFusion and Lucee CFML) - note that the outer loop is labeled searchLoop: and the inner loop is labeled targetLoop::

<cfscript>

	// Fuzzy matches.
	writeOutput( isFuzzyMatch( "horse", "s" ) & "<br />" );
	writeOutput( isFuzzyMatch( "horse", "hs" ) & "<br />" );
	writeOutput( isFuzzyMatch( "horse", "horse" ) & "<br />" );

	// No matches.
	writeOutput( isFuzzyMatch( "horse", "horses" ) & "<br />" );
	writeOutput( isFuzzyMatch( "horse", "test" ) & "<br />" );
	writeOutput( isFuzzyMatch( "horse", "" ) & "<br />" );

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

	/**
	* I determine if the given target text is a fuzzy-match for the given search text.
	*/
	public boolean function isFuzzyMatch(
		required string targetValue,
		required string searchValue
		) {

		var searchChars = searchValue.listToArray( "" );
		var targetChars = targetValue.listToArray( "" );
		var matchFound = false;

		searchLoop:
		while ( searchChars.len() ) {

			var s = searchChars.shift();

			targetLoop:
			while ( targetChars.len() ) {

				var t = targetChars.shift();

				// We found a matching CHARACTER in the target string.
				if ( s == t ) {

					if ( ! searchChars.len() ) {

						matchFound = true;
						// If we've run out of search-characters to consume, it means that
						// the entirety of the search keyword was located (in parts)
						// within the target text. In other words, we HAVE a fuzzy-match.
						// Yay! At this pointer, there is nothing left to search and we
						// can break out of BOTH the INNER and OUTER loops.
						break searchLoop;

					}

					// If we still have more search characters to consume, move onto the
					// NEXT ITERATION of the OUTER loop, and the next search character.
					continue searchLoop;

				}

			}

			// If we've fully consumed the target characters, there's no sense in
			// continuing to consume the search characters - we will not find a match.
			break;

		}

		return( matchFound );

	}

</cfscript>

NOTE: In this code, there is no need for me to label the inner loop, targetLoop: - I'm just doing it to demonstrate that it can be done.

As you can see, whenever I match a search character against a target character, I perform a control-flow operation that references the outer loop, searchLoop:. If I've consumed all of the search characters, I break out of outer loop (essentially ending the algorithm); and, if there are more search characters to consume, I continue onto to the next iteration of the outer loop. Both of these operations are performed from within the inner loop.

If we run this ColdFusion code, we get the expected outcome:

true    <!-- isFuzzyMatch( "horse", "s" ) -->
true    <!-- isFuzzyMatch( "horse", "hs" ) -->
true    <!-- isFuzzyMatch( "horse", "horse" ) -->

false   <!-- isFuzzyMatch( "horse", "horses" ) -->
false   <!-- isFuzzyMatch( "horse", "test" ) -->
false   <!-- isFuzzyMatch( "horse", "" ) -->

This demo uses script-based loops; but, apparently this is also available in tag-based loops as well.

I'm not sure if the value-add of this technique outweighs the potential cognitive cost. But, it's probably worth having this loop labeling concept tucked away in the back of my mind in case I ever need it.

Epilogue: Tag-Based Labeled Loops

Above, I mentioned that labeled loops were also available in tag-based CFML. As such, I thought it might be worth translating the previous script-based syntax into tags just to see what that would look like, especially since the Adobe ColdFusion documentation for CFBreak and CFContinue mention nothing about this. Thankfully, the Lucee CFML documentation does mention that the label should be provided as a string literal.

<cfoutput>

	<!--- Fuzzy matches. --->
	#isFuzzyMatch( "horse", "s" )#<br />
	#isFuzzyMatch( "horse", "hs" )#<br />
	#isFuzzyMatch( "horse", "horse" )#<br />

	<!--- No matches. --->
	#isFuzzyMatch( "horse", "horses" )#<br />
	#isFuzzyMatch( "horse", "test" )#<br />
	#isFuzzyMatch( "horse", "" )#<br />

</cfoutput>

<cffunction name="isFuzzyMatch" returntype="boolean" output="false">

	<cfargument name="targetValue" type="string" required="true" />
	<cfargument name="searchValue" type="string" required="true" />

	<cfset var searchChars = searchValue.listToArray( "" ) />
	<cfset var targetChars = targetValue.listToArray( "" ) />
	<cfset var matchFound = false />

	<cfloop label="searchLoop" condition="searchChars.len()">

		<cfset var s = searchChars.shift() />

		<cfloop label="targetLoop" condition="targetChars.len()">

			<cfset var t = targetChars.shift() />

			<!--- We found a matching CHARACTER in the target string. --->
			<cfif ( s == t )>

				<cfif ! searchChars.len()>

					<cfset matchFound = true />
					<!---
						If we've run out of search-characters to consume, it means that
						the entirety of the search keyword was located (in parts)
						within the target text. In other words, we HAVE a fuzzy-match.
						Yay! At this pointer, there is nothing left to search and we
						can break out of BOTH the INNER and OUTER loops.
					--->
					<cfbreak searchLoop />

				</cfif>

				<!---
					If we still have more search characters to consume, move onto the
					NEXT ITERATION of the OUTER loop, and the next search character.
				--->
				<cfcontinue searchLoop />

			</cfif>

		</cfloop>

		<!---
			If we've fully consumed the target characters, there's no sense in
			continuing to consume the search characters - we will not find a match.
		--->
		<cfbreak />

	</cfloop>

	<cfreturn matchFound />

</cffunction>

Notice that the CFLoop now has a label attribute; and, that the searchLoop reference is boolean attribute on both the CFContinue and CFBreak tags. This tag-based code, of course, gives us the same output as the script-based code above ... though, with significantly more syntax.

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

Reader Comments

7 Comments

Very interesting. I've been coding in CF since late 1990's, and I had no idea the labels existed. One real-world example using them would be scraping documents for specific types of data. (Scraping is one of my specialties, and trust me, there's a lot of conditional logic going on in scraping.) But hving said that, yeah.... using labels badly could lead to spaghetti-y code like these old-school BASIC programs using GOTO's too much. :-)

15,811 Comments

@Angeli,

To be fair, I think labeled-loops was only added in recent versions of ColdFusion -- the one article that I found on this references ACF 2021 as being the version in question. But, I can't find a "history" section on the docs page, like they used to have.

Re: scraping, I remember waaaaay back in the day, Yahoo had some service where you could turn any webpage into a SQL data-source. I think they called it YQL (Yahoo Query Language). I did a couple of little things where I used that to scrape some pages. But, I think they got rid of that service pretty quickly 😆

3 Comments

Great, but in the example you don't need any of this. On line 37 instead of setting matchFound = true, just return true. No reason to keep going any further. You don't even need the matchFound variable. Replace line 53 with a normal break, since breaking from inner loop will do same as continuing the outer loop. Line 67, just return false since if it was ever true it would have returned already.

<cfloop condition="searchChars.len()">
	<cfset var s = searchChars.shift() />
	<cfloop condition="targetChars.len()">
		<cfset var t = targetChars.shift() />
		<cfif ( s == t )>
			<cfif ! searchChars.len()>
				<cfreturn true />
			</cfif>
			<cfbreak />
		</cfif>
	</cfloop>
	<cfbreak />
</cfloop>
<cfreturn false />
15,811 Comments

@Emre,

This is a great point-out. And, in fact, in my original code, I have the early return(true) to short-circuit the evaluation. I put the matchFound variable in there in order to explore the labeled-looping a bit more.

That said, your second break will break the algorithm. Essentially, you stop searching after the first character match. Imagine that my target text is horse and you're fuzzy searching for hoe. In your loop h would match, so you break out of the inner loop. But then, you immediately break out of the outer loop as well, leading to a false (which should have been true). That's why I have the continue searchLoop.

3 Comments

I think my previous post didn't account for the last break in the outer while loop. Instead of using a break when it reaches the end, it needs a condition in the outer while, but below is the same code without the labeled out-of-sequence-breaking loops.

function isFuzzyMatch(targetValue, searchValue) {
	var searchChars=searchValue.listToArray( "" );
	var targetChars=targetValue.listToArray( "" );
	while(searchChars.len() && targetChars.len()) {
		var s=searchChars.shift();
		while(targetChars.len()) {
			var t=targetChars.shift();
			if(s==t) {
				if(!searchChars.len()) { return true; }
				break;
			}
		}
	}
	return false;
}
15,811 Comments

@Emre,

Ah, adding a check for both the search and target characters length in the outer-loop is clever. Yes, I believe this would get you want you wanted.

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