Programmers coming to functional languages for the first time cannot imagine life without variables. I address this head-on in the Clojure book. In Section 2.7 (free download here), I port an imperative method from the Apache Commons Lang to Clojure. First the Java version:
// From Apache Commons Lang, http://commons.apache.org/lang/
public static int indexOfAny(String str, char[] searchChars) {
if (isEmpty(str) || ArrayUtils.isEmpty(searchChars)) {
return -1;
}
for (int i = 0; i < str.length(); i++) {
char ch = str.charAt(i);
for (int j = 0; j < searchChars.length; j++) {
if (searchChars[j] == ch) {
return i;
}
}
}
return -1;
}
And now the Clojure code. I have shown the supporting function indexed
as well:
There are many things I like about the Clojure version, but I want to focus on something I didn't mention already in the book. A reader thought the Clojure version did too much work:
...the [Java] version can be seen as *more efficient* when a match is found because scanning stops right there, whereas "indexed" constructs the whole list of pairs, regardless of whether or not a match WILL be found....
The reader's assumption is reasonable, but incorrect. Clojure's sequence library functions are generally lazy. So the call to indexed
is really just a promise to generate indexes if they are actually needed.
To see this, create a logging-seq
that writes to stdout every time it actually yields an element:
Now, you can add logging-seq
to indexed
so that each element of indexed
is of the form [index, element, logged-element]
.
Test the modified indexed
function at the Clojure REPL:
As you can see, the indexed sequence is only produced as needed. (At the REPL it is needed to print the return value.)
Finally, you can test indexed-of-any
and see that Clojure only produces enough of the sequence to get an answer. For a match on the first character, it only goes to the first character:
(index-of-any "foo" #{\f})
Iterating over f
0
If there is no match, index-of-any
has to traverse the entire string:
(index-of-any "foo" #{\z})
Iterating over f
Iterating over o
Iterating over o
nil
So give up on those variables, and live lazy!