Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
Ben Nadel at CFUNITED 2010 (Landsdown, VA) with: Colleen Lanham and Demian Holmberg and Robert Dix and Paul Carney and Eric Pfleckl
Ben Nadel at CFUNITED 2010 (Landsdown, VA) with: Colleen Lanham , Demian Holmberg@dah007 ) , Robert Dix@p_robert_dix ) , Paul Carney@perpetapaul ) , and Eric Pfleckl@epfleckl )

Strange arrayContains() Behavior In For-Loop In ColdFusion 10

By Ben Nadel on
Tags: ColdFusion

Yesterday, I wrote some code that used arrayContains(). Now, I don't use the arrayContains() or the arrayFind() ColdFusion methods very often. To be honest, it just doesn't come up as a use-case that often. And, when it does, I tend to shy away from these functions because I've seen them go "wrong" in the past. But, it's been years since they were introduced and I'm on a different version of ColdFusion (10), so I wanted to give them another try. In a loosely-typed language, however, understanding the interoperability between strings and numbers can be a bit of stressful guessing game. To calm my nerves, I wanted to do a little experiment. And, while I was performing this experiment, I stumbled upon a seriously odd arrayContains() behavior in the context of a for-loop.


 
 
 

 
 
 
 
 

The experiment was going to be basic: have an array of mixed-type "simple" values and then try to search for those values using mixed-type inputs. At first, I started to code individual use-cases. But, this seemed tedious; so I switched over to using a for-loop. Only, when I did that, I noticed that some of the results changed! To get a clearer picture, I ended up creating a demo that runs the same arrayContains() searches using both a for-loop and a set of hard-coded function calls:

  • <cfscript>
  •  
  • // Build up a collection of mixed-type values. These are all "simple" values, but
  • // some of them are more strongly associated with a given data type.
  • values = [
  • 1,
  • "2",
  • "3.0",
  • javaCast( "int", 4 ),
  • javaCast( "double", 5 ),
  • javaCast( "long", 6 ),
  • 7.0,
  • javaCast( "string", "8" ),
  • javaCast( "char", "9" ),
  • javaCast( "float", "10.0" )
  • ];
  •  
  • writeOutput( "<h2>#server.coldfusion.productVersion#</h2>" );
  • writeOutput( "<div style='float: left ; width: 150px ;'>" );
  • writeOutput( "<h3>Loop</h3>" );
  •  
  • // First, let's test the arrayContains() through a loop.
  • for ( i = 1 ; i <= 10 ; i++ ) {
  •  
  • writeOutput( "Testing #i#: <br />" );
  • writeOutput( arrayContains( values, i ) & "<br />" );
  • writeOutput( arrayContains( values, "#i#" ) & "<br />" );
  • writeOutput( arrayContains( values, "#i#.0" ) & "<br />" );
  • writeOutput( arrayContains( values, javaCast( "int", i ) ) & "<br />" );
  • writeOutput( arrayContains( values, javaCast( "float", i ) ) & "<br />" );
  • writeOutput( "<br />" );
  •  
  • }
  •  
  • writeOutput( "</div>" );
  • writeOutput( "<div style='float: left ; width: 150px ;'>" );
  • writeOutput( "<h3>Hard-Coded</h3>" );
  •  
  • // Now, let's run the same exact tests, but with hard-coded values.
  • writeOutput( "Testing 1: <br />" );
  • writeOutput( arrayContains( values, 1 ) & "<br />" );
  • writeOutput( arrayContains( values, "1" ) & "<br />" );
  • writeOutput( arrayContains( values, "1.0" ) & "<br />" );
  • writeOutput( arrayContains( values, javaCast( "int", 1 ) ) & "<br />" );
  • writeOutput( arrayContains( values, javaCast( "float", 1 ) ) & "<br />" );
  • writeOutput( "<br />" );
  •  
  • writeOutput( "Testing 2: <br />" );
  • writeOutput( arrayContains( values, 2 ) & "<br />" );
  • writeOutput( arrayContains( values, "2" ) & "<br />" );
  • writeOutput( arrayContains( values, "2.0" ) & "<br />" );
  • writeOutput( arrayContains( values, javaCast( "int", 2 ) ) & "<br />" );
  • writeOutput( arrayContains( values, javaCast( "float", 2 ) ) & "<br />" );
  • writeOutput( "<br />" );
  •  
  • writeOutput( "Testing 3: <br />" );
  • writeOutput( arrayContains( values, 3 ) & "<br />" );
  • writeOutput( arrayContains( values, "3" ) & "<br />" );
  • writeOutput( arrayContains( values, "3.0" ) & "<br />" );
  • writeOutput( arrayContains( values, javaCast( "int", 3 ) ) & "<br />" );
  • writeOutput( arrayContains( values, javaCast( "float", 3 ) ) & "<br />" );
  • writeOutput( "<br />" );
  •  
  • writeOutput( "Testing 4: <br />" );
  • writeOutput( arrayContains( values, 4 ) & "<br />" );
  • writeOutput( arrayContains( values, "4" ) & "<br />" );
  • writeOutput( arrayContains( values, "4.0" ) & "<br />" );
  • writeOutput( arrayContains( values, javaCast( "int", 4 ) ) & "<br />" );
  • writeOutput( arrayContains( values, javaCast( "float", 4 ) ) & "<br />" );
  • writeOutput( "<br />" );
  •  
  • writeOutput( "Testing 5: <br />" );
  • writeOutput( arrayContains( values, 5 ) & "<br />" );
  • writeOutput( arrayContains( values, "5" ) & "<br />" );
  • writeOutput( arrayContains( values, "5.0" ) & "<br />" );
  • writeOutput( arrayContains( values, javaCast( "int", 5 ) ) & "<br />" );
  • writeOutput( arrayContains( values, javaCast( "float", 5 ) ) & "<br />" );
  • writeOutput( "<br />" );
  •  
  • writeOutput( "Testing 6: <br />" );
  • writeOutput( arrayContains( values, 6 ) & "<br />" );
  • writeOutput( arrayContains( values, "6" ) & "<br />" );
  • writeOutput( arrayContains( values, "6.0" ) & "<br />" );
  • writeOutput( arrayContains( values, javaCast( "int", 6 ) ) & "<br />" );
  • writeOutput( arrayContains( values, javaCast( "float", 6 ) ) & "<br />" );
  • writeOutput( "<br />" );
  •  
  • writeOutput( "Testing 7: <br />" );
  • writeOutput( arrayContains( values, 7 ) & "<br />" );
  • writeOutput( arrayContains( values, "7" ) & "<br />" );
  • writeOutput( arrayContains( values, "7.0" ) & "<br />" );
  • writeOutput( arrayContains( values, javaCast( "int", 7 ) ) & "<br />" );
  • writeOutput( arrayContains( values, javaCast( "float", 7 ) ) & "<br />" );
  • writeOutput( "<br />" );
  •  
  • writeOutput( "Testing 8: <br />" );
  • writeOutput( arrayContains( values, 8 ) & "<br />" );
  • writeOutput( arrayContains( values, "8" ) & "<br />" );
  • writeOutput( arrayContains( values, "8.0" ) & "<br />" );
  • writeOutput( arrayContains( values, javaCast( "int", 8 ) ) & "<br />" );
  • writeOutput( arrayContains( values, javaCast( "float", 8 ) ) & "<br />" );
  • writeOutput( "<br />" );
  •  
  • writeOutput( "Testing 9: <br />" );
  • writeOutput( arrayContains( values, 9 ) & "<br />" );
  • writeOutput( arrayContains( values, "9" ) & "<br />" );
  • writeOutput( arrayContains( values, "9.0" ) & "<br />" );
  • writeOutput( arrayContains( values, javaCast( "int", 9 ) ) & "<br />" );
  • writeOutput( arrayContains( values, javaCast( "float", 9 ) ) & "<br />" );
  • writeOutput( "<br />" );
  •  
  • writeOutput( "Testing 10: <br />" );
  • writeOutput( arrayContains( values, 10 ) & "<br />" );
  • writeOutput( arrayContains( values, "10" ) & "<br />" );
  • writeOutput( arrayContains( values, "10.0" ) & "<br />" );
  • writeOutput( arrayContains( values, javaCast( "int", 10 ) ) & "<br />" );
  • writeOutput( arrayContains( values, javaCast( "float", 10 ) ) & "<br />" );
  • writeOutput( "<br />" );
  •  
  • writeOutput( "</div>" );
  •  
  • </cfscript>

As you can see, the target collection contains a mixed bag of numbers, strings, and Java-cast values (which are also basically "simple" values). I'm then running a series of arrayContains() calls both in a for-loop and in a set of explicitly provided values. I then ran this code in both ColdFusion 10 and ColdFusion 11 and I get the following output:

NOTE: I've placed the outputs side-by-side for easier comparison.


 
 
 

 
 arrayContains() behavior changes in ColdFusion 10 and ColdFusion 11. 
 
 
 

First of all, let's just be clear that these function calls do seem to work for simple strings and numbers. Meaning, when I'm using vanilla "ColdFusion simple data types", like 2 and "3", the type coercion works well in the arrayContains() in both the for-loop as well as in the explicitly provided input.

But, when we start looking at javaCast() values, things get a little inconsistent. For example, look at case 4, which is javaCast( "int", 4 ) in the values collection. In the for-loop, these two return NO:

  • writeOutput( arrayContains( values, i ) & "<br />" );
  • writeOutput( arrayContains( values, "#i#" ) & "<br />" );

... but when hard-coded, these two return YES:

  • writeOutput( arrayContains( values, 4 ) & "<br />" );
  • writeOutput( arrayContains( values, "4" ) & "<br />" );

The only difference here is that the loop iterator, "i", was replaced with a hard-coded 4. I can't think of any reason why this would affect the results. Unless maybe the for-loop is using some strange data-type for the iterator.

Luckily, all of these inconsistencies seem to be fixed in ColdFusion 11. And, in fact, more of the comparisons to Java-cast values seem to work as one might expect. So, that's a win.

Languages that have automatic type coercion are interesting. On the one hand, it can make them super easy to work with in many cases. But, on the other hand, it can make value comparisons a bit of guessing game, especially when those comparisons are black-boxed behind something like arrayContains(). Unfortunately, I'm still on ColdFusion 10 in production, so I can use arrayContains(); but, will probably only feel comfortable using it in the most basic situations.




Reader Comments

@All,

As a quick aside, the results are exactly the same if you use arrayFind() instead of arrayContains().

Reply to this Comment

Ugggg! I just got burned by this ... again. I'm so irritated that this doesn't just *work* as expected. I had to create an arrayContainsSafe() method. Meh.

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.