Tuesday, 5 August 2014

CFML: What was that array.each() quiz all about?

G'day:
Yesterday I posed a quiz question to you: "Quick quiz: checking your expectations", which was asking how you'd expect this code to behave:

//each.cfm
a = ["a"];
a[3] = "c";

a.each(function(v,i,a){
    writeOutput("value: #v#<br>");
});

Why was I asking?

Well because on Railo it behaves like this:

value: a
value: c


Whereas ColdFusion 11 behaves like this:

value: a

The web site you are accessing has experienced an unexpected error.
Please contact the website administrator. 


The following information is meant for the website developer for debugging purposes.
Error Occurred While Processing Request

An exception occurred while calling the function each.

Variable V is undefined.

Ignore the error, that's not important here. The importance is that Railo iterates over the array elements, whereas CF iterates over the array indexes. There's no element at index position 2 of the array, so Railo doesn't include it in its iteration. However there is still an index point "2", so ColdFusion iterates over it. The error is because CF cannot handle null values properly, not anything to do with the Array.each() function. I could make that code null-safe by doing this:

writeOutput("value: #v?:'null'#<br>");

Thus working around CF's inability to deal with nulls, and we get this output:

value: a
value: null
value: c


But each CFML engine is approaching the task in a different way, which is... less than ideal. What's more, I can see a case for either approach, which is why I asked you lot what you'd expect to happen. All things being equal, the language matching developers' expectations is the key.

The people who bothered responding erred toward expecting ColdFusion's approach here. And given that ColdFusion released their implementation first (CF10, May 2012), compared to Railo 4's in July 2012, I think the onus is on Railo to address this. They might have a sound rationale for taking the approach that they did, but whatever that rationale is needs to outweigh the benefit of staying compatible between engines.

My own opinion erred towards Railo's approach making more sense: we're iterating over the array here... and the important information in an array is its elements, not what their index points are (which is just a means to an end). However on the other hand, Railo's approach is irrevocably "losing" information that ColdFusion's approach preserves, so ColdFusion's approach is more flexible here.

I had a quick shufti at some other languages I'm capable of writing a for/each loop in, to see if there was a clear precedent of one over the other. Apologies for the quality of my code here:

// each.js
a = ["a"];
a[3] = "c";

a.forEach(function(v){
    console.log("value: " + v);
});

value: a
value: c


# each.rb
a = []
a[0] = "a"
a[2] = "c"

a.each do |v|
    puts "value: #{(v || 'nil')}" 
end

value: a
value: nil
value: c


<?php
// each.php
$a = array();
$a[0] = "a";
$a[2] = "c";

foreach ($a as $v){
    echo "value: {$v}\r\n";
}
?>

value: a
value: c


// each.groovy
a = ["a"]
a[2] = "c"
for (v in a){
    println "value: ${v}"
}

value: a
value: null
value: c


#each.py
a = []
a.insert(0, "a")
a.insert(2, "c")
for v in a:
    print "value: ", v

value: a
value: c


So Ruby and Groovy iterate over the index points; JavaScript, PHP and Python iterate over the array elements. I'm sure if I tested other languages, I'd see more of the same: no concrete precedent.

This being the case, I'm calling this a bug in Railo's implementation, and raising it accordingly: RAILO-3151.

--
Adam