recur always uses tail recursion, whether you are repeating in a loop or in the head of a function. The problem is with remove calls. remove calls first to get the element from base seq and checks to see if that element is valid. If the base seq was created by the remove call, you will get another first call. If you call remove 20,000 times on the same seq, calling first requires calling first 20,000 times, and none of the calls can be tail recursive. Therefore, a mistake.
Changing (remove ...) to (doall (remove ...)) fixes the problem, as it prevents endlessly stacking remove calls (each one is fully applied immediately and returns a specific seq, not lazy seq). I think this method only ever stores one list of candidates in memory at a time, although I'm not sure about that. If so, it is not too inefficient, and a little testing shows that in reality it is not much slower.
Retief
source share