“Creativity is allowing yourself to make mistakes. Art is knowing which ones to keep.”
— Scott Adams
When I discovered the eerie truth about JavaScript’s ‘in’ operator, I thought that it couldn’t get any weirder. But boy-oh-boy, was I wrong!
Let’s first recap what’s so bad about JavaScript’s implementation of ‘in’. If you have a list (aka. array) in JavaScript and use the ‘in’ operator to test for membership, you are asking for trouble:
1 2 3 4 5 6 7 |
> array = [4, 2, 1] > 0 in array true > 4 in array false |
Contrary to what you might expect, the ‘in’ operator doesn’t test whether the left-hand side value is a member of the array, but rather whether it is a valid index into the array. This makes absolutely no sense for arrays, but it does for dictionaries, since it tests whether a given key is present or not:
1 2 3 4 5 6 7 |
> dict = {4 : 'four', 2 : 'two', 1 : 'one'} > 0 in dict false > 4 in dict true |
The moral of the story? Don’t use the ‘in’ operator in JavaScript on arrays to test for membership; with dictionaries everything is fine.
The other day, I found another shocking behavior of the ‘in’ operator, but this time in Groovy; not when used on arrays — when used on dictionaries! Consider this routine. It has two parameters, a list of words (the input text) and a dictionary which contains an entry for every word whose number of occurrences in the input text is to be counted:
1 2 3 4 5 6 7 8 9 10 11 12 |
def countWords(wordsList, wordsToCount) { // For every word in list. wordsList.each { word ->; // If word is a word that is to be counted. if (word in wordsToCount) { // Increase count. ++wordsToCount[word] } } } |
The initial word counts in ‘wordsToCount’ are set to 0; the word counts are increased for every corresponding word found in ‘wordsList’. For instance:
1 2 3 4 5 6 |
> def myText = ['spam', 'blah', 'eggs', 'foo', 'eggs', 'buzz'] > def myWordsToCount = ['spam' : 0, 'eggs' : 0, 'ham' : 0] > countWords(myText, myWordsToCount) > print myWordsToCount |
should print:
1 2 3 |
[spam:1, eggs:2, ham:0] |
At least, this is what most people would expect and what one would get in Python or JavaScript. Alas, what you will get in Groovy is this:
1 2 3 |
[spam:0, eggs:0, ham:0] |
When used on dictionaries, Groovy’s ‘in’ operator not only checks whether the given key on its left-hand side exists in the dictionary, it also overeagerly checks if the corresponding value is ‘true’ according to the Groovy Truth Rules; that is, 0, null, empty strings and empty lists will make the ‘in’ operator return false.
The ‘in’ operator should only test for membership; that is, it should test if the left-hand side is in the collection on the right-hand side. On no account should it interpret an element’s value.
Not following a well-established convention is bad enought, but being inconsistent within the same language is a lot worse: when used with lists/arrays, the ‘in’ operator just checks for membership and doesn’t care a bag of beans about the value of a member:
1 2 3 4 5 6 7 8 9 10 11 |
> def mylist = [ 0, 1, null, "", [] ] > 0 in mylist true > null in mylist true > "" in mylist true > [] in mylist true |
It won’t get any weirder — it simply can’t!