Way back when I was just starting Clojure, I came across a blog post that said that loop/recur was kind of like code smell, with the implication that it was a good tool for some cases, but if you were using it you had better justify why. The actual reasoning, though, did a terrible job of convincing me of the truth of the statement because as far as I could tell, it boiled down to “loop/recur offends my sensibilities and is inelegant!” which is fine, don’t get me wrong, we’ve all got our little hang-ups, but just because something’s not pretty isn’t a reason to be suspicious of its usage! Besides, loop/recur is how you implement non-stack-eating recursion, and recursion the most basic construct in a functional programming language, right? Recursion’s how you do things, because you can’t very well stick a while statement in here, can you?
Then the blog post went on to say that you should favor transformation over recursion, and that confused me too, because yes, right, I follow you as to why transformation’s better, but how do you implement that without recursion (and, presumably, generous usage of loop/recur to keep your stack from hacking itself up if you put in too much data)?
So that stuck with me, and now that I’ve had some more experience I actually agree with most of what he said regarding those two points, but I think that he did possibly The Most Terrible Job explaining why loop-recur is something to be suspicious of. So let’s talk about why. Why is loop/recur something to be suspicious of?
Loop/recur is something to be suspicious of because Clojure has many, many in-built, higher-level functions that will manage the underlying recursion for you. These functions range from purely functional (map, reduce, filter) to not really functional at all (doall, doseq) and cover the vast majority of cases where you might need to use loop/recur. They also tend to be easier to use than loop/recur, and the final code will be smaller, easier to read, and easier to understand. Therefore, if you’re using loop/recur, there’s a very strong possibility that whatever you’re doing could be done faster and more easily by using of the these existing functions.
That’s why loop/recur might be looked upon with suspicion – it’s a low-level construct which is sometimes necessary, but which should probably be avoided. It’s like using malloc in C++ – you never want to do this unless there’s some very specific situation in which you need to, I don’t know, mess around with a buffer or something, in which case it is totally appropriate.
With that said, here is A Short List Of Things You Could Use Loop/Recur To Do, But Probably Shouldn’t:
- You want to do something to every member of a sequence – use map instead.
- You want to build a new vector from an old vector – use for instead.
- You want to take in a sequence, and build a single result, derived from the data in the sequence – use reduce instead.
- You want to take a subsequence of a sequence – use filter instead.
There are a bunch more way that Clojure has to manage recursion that aren’t covered here, but basically, if you find yourself using loop/recur, take a few seconds to stop and say “Is there a function that does this for me?” If there isn’t one, go right ahead, but I found that most of the time I was using loop/recur, I really should have been using reduce, or some other built-in function.