G'day:
I've been staring at this code (/variations thereof) for a week or so now (since
JavaScript: running Jasmine unit tests from the CLI; more specifically my JavaScript version of the code I rewrote from "
Some CFML code that doesn't work"). I noticed an idiosyncrasy in the first JavaScript version of that code which didn't make sense to me at first. And I
ass-u-me`d that my previous expectations made sense and JavaScript was being weird. Then I ran the equivalent code in some other languages and now... not so sure. So here we are: I'm writing a blog article about code I'm not sure I'm completely comfortable with.
It does not help that I'm on my eighth pint of Guinness for the afternoon. But perhaps only as far as my typing goes (which is proving to be a
real challenge).
Here's the general gist of what I was trying to do with CFML:
// closure.cfm
letters = ["a","b","c","d","e"];
remappedLetters = letters.map(function(number,index){
var localCopyOfTheseLetters = duplicate(letters);
letters.deleteAt(1);
return localCopyOfTheseLetters;
});
remappedLetters.each(function(series){
writeOutput(series.toList(" ") & "<br>");
});
Don't worry so much about running that: it does not do what I want (mostly), but more what I'm trying to do, and my expectations of the results.
What I want from this code is
to iterate over the letters array, and for each element of it
return the whole array from that point. So I'm not really using the callback's value, I'm just leveraging the fact that the
map()
iteration method loops over each element of the array, so I get to defined - element by element - its replacement.
On Lucee - which is what I tested this with - I get what I want:
a b c d e
b c d e
c d e
d e
e
And this is what I mentally leveraged when working out my code for that "
Some CFML code that doesn't work" article. The rest of my logic was predicated on that approach working.
When I tried the same logic on JavaScript:
// closure.js
var letters = ["a","b","c","d","e"];
var remappedLetters = letters.map(function(number,index){
var localCopyOfTheseLetters = letters.slice();
letters.shift();
return localCopyOfTheseLetters;
});
remappedLetters.forEach(function(series){
console.log(series.join(" "));
});
I get a different result:
C:\src\otherLanguages\js\arrays\map\changeOriginal\js>node closure.js
a b c d e
b c d e
c d e
Huh? Why does it stop after three element? I'm iterating over a five-element array after all. I surmise that under the hood JavaScript is using some sort of
Iterator.next()
operation. Given I am changing the array I'm iterating over via closure, by the time I get to the third iteration of my
map()
call, I've lopped two elements off the array, so its length is three, so the
map()
process exits when
Iterator.next()
rechecks where it is in the array, and finds out it's at the end. This is entirely supposition, but it seems reasonable.
But initially I was thinking "dumb-arse JavaScript, if I call a method on an object, then the method should be called on the object's current state!". This makes sense for a one-off method, but does it make sense for an iterative method? Hmmm. I had not thought about that. And I had no answer, and given my two comparative cases (Lucee, JavaScript) behaved differently, I didn't know what ought to be the "right" answer. And by "right" I mean "industry standard". I erred towards JavaScript being right, and Lucee being wrong.
Running this example on ColdFusion behaves like JavaScript. But to be honest: I back Adobe to get things right even less than I do LAS, so I filed that as "nice to know".
I did my usual thing of testing out my limited repertoire of other languages running equivalent code.