Yesterday, at InVision, I was writing an algorithm in which I needed to build several one-dimensional arrays. And, in some cases, I was using all simple values; but, in other cases, I was using a mixture of simple values and other arrays. To keep my calling code clean, I abstracted the logic into a flattenArray()
method that would take N-arguments and then smoosh all of those arguments down into a single array. The method I created worked fine, but it just didn’t look “right”. I wasn’t vibing it. As such, I wanted to step back and try creating a flatten method with a variety of different syntaxes to see which strikes the right balance between simplicity, elegance, and readability (which is all highly subjective).
In my case, I only needed the method to flatten one level deep – I wasn’t going to be using any deeply-nested arrays. As such, at least my logic didn’t require any recursion; so, that’s already a win from the get-go. Flattening an array in this manner turns:
[a, [b, c], d]
… into:
[a, b, c, d]
Note that the [b,c]
array was “unwrapped” and merged into the final result.
Here are four different approaches that I can think of to flatten an array in ColdFusion (without recursion). I am using .reduce()
, .each()
, and two different types of loops:
<cfscript>
a = [ "hello", "world" ];
b = "simple";
c = [ "cool", "beans" ];
// NOTE: All of these methods only flatten ONE LEVEL down.
dump( arrayToList( flatten( a, b, c ) ) );
dump( arrayToList( flatten2( a, b, c ) ) );
dump( arrayToList( flatten3( a, b, c ) ) );
dump( arrayToList( flatten4( a, b, c ) ) );
// ------------------------------------------------------------------------------- //
// ------------------------------------------------------------------------------- //
// APPROACH ONE: Using the .reduce() method.
public array function flatten() {
var results = arguments.reduce(
( reduction, key, value ) => {
return( reduction.append( value, isArray( value ) ) );
},
[]
);
return( results );
}
// APPROACH TWO: Using CFLoop for array values.
public array function flatten2() {
var results = [];
loop
item = "local.value"
array = arguments
{
results.append( value, isArray( value ) );
}
return( results );
}
// APPROACH THREE: Using a for-in loop.
public array function flatten3() {
var results = [];
for ( var key in arguments ) {
results.append( arguments[ key ], isArray( arguments[ key ] ) );
}
return( results );
}
// APPROACH FOUR: Using an each iterator.
public array function flatten4() {
var results = [];
arguments.each(
( key, value ) => {
results.append( value, isArray( value ) );
}
);
return( results );
}
</cfscript>
When we run this code, all four flatten methods yield the same output:

So, these flatten methods all “work”, but which one is the “best”?
As always, one of my first instincts is to use the .reduce()
method. There is something so alluring about .reduce()
– it has an air of sophistication and an elegance underscored by classical computer science. I actually feel smarter when I write a .reduce()
method.
That said, just about every time I’m done writing a .reduce()
method, I step back and just feel so meh about the whole thing. .reduce()
always feels way too wordy with lots of values and syntactic noise. As such, 9-in-10 times, I scrap the .reduce()
approach and use a simplified loop.
At work, I ended up going with approach two: using the CFLoop
tag to iterate over the arguments collection. One thing that I love about the CFLoop
tag is that it can expose a number of optional attributes that can surface different aspects of the iteration. Meaning, when iterating over a Struct, I can use both the key
and value
attributes; or, just one of them. Similarly, with an Array, I can use both the item
and index
attributes; or, just one of them. In other words, the CFLoop
tag allows me to define only the parts of the loop that I actually need to consume. In my case, I’m exposing the item
aspect of Array iteration without the index
since I don’t actually need the index
.
ASIDE: The
arguments
scope is neither an Array nor a Struct – it’s a specialized scope that has both Array and Struct behaviors, which makes it some kind of wonderful. CallingisArray(arguments)
andisStruct(arguments)
both yieldtrue
.
The CFLoop
tag approach also feels like it does the most work with the least amount of syntax.
If, instead of creating a variadic method (a method that receives a dynamic number of arguments), I created a method that received a single argument which was an array, then I would probably go with the for-in
style loop:
<cfscript>
public array function flatten( required array values ) {
var results = [];
for ( var value in values ) {
results.append( value, isArray( value ) );
}
return( results );
}
</cfscript>
In my case, since I am using a variadic method, the for-in
approach uses Struct iteration (of the arguments
scope), not Array iteration. Which means I have to perform a key-based look-up of the iteration value.
I know this stuff is highly subjective. When I look at the different techniques, I just have to listen to my gut and go with the method that feels like it strikes the right balance of qualities.
One opinion that I often hear people in other programming communities voice is that there should be one idiomatic way to do things. In ColdFusion, this is the farthest thing from the truth. With CFML, there are multiple ways to do most things; and, I find that to be a huge strength of the platform. You get to pick the approach that feels right for the context – you don’t have to shoe-horn all manner of variety into some inflexible set of constructs.
Want to use code from this post?
Check out the license.