Segfault? That sounds scary! I assume you mean a stack overflow error.
When I run the function I start to get stack overflow errors at about 1000. So why the stack overflows? It has to do with laziness. The fix is to wrap the call to remove in a doall.
Reduce will iterate through each element in the sequence given as the third argument and keep a state along the way. This state is initialized as the rage of integers from 2 to n+1. The state is updated at each iteration using remove. However, since remove is lazy it won't actually do anything. Remove will return an object that can generate a sequence on demand, based on the sequence it was given. I will try to explain this thorugh an example:
(reduce (fn [xs x] (remove #{x} xs)) coll (range 4))
The above expression will return a sequence of the elements of coll, but with numbers 0 to 3 filtered out. To explain what happens at run time I will invent a new notation: I will write the runtime value of (remove #{x} xs)
as «I.O.U. a seq like xs but with x removed»
. The value of the reduce expression is then:
«I.O.U. a seq like
«I.O.U. a seq like
«I.O.U. a seq like
«I.O.U. a seq like 'coll' but with 0 removed»
but with 1 removed»
but with 2 removed»
but with 3 removed»
Each call to remove adds a wrapping "I.O.U.". This causes problems when you finally try to access the first value of the resulting seq (the outermost "I.O.U." value). When a lazy-seq object is traversed, it is first checked whether its value has been calculated. If the value is already done, the value is simply returned. If it's not, then it's calculated, stored, and returned.
The problem arises when one lazy-seq ("I.O.U." value) needs to force another lazy-seq to be able to perform its work, because one stack frame is needed per lazy-seq realization. (A stack frame is needed in order to remember where to return to when the second lazy-seq is done.) If you have 1000 nested "I.O.U." values, you need 1000 stack frames to realize them (assuming all of them were unrealized initiallly).
The solution is to use the doall function from the Clojure standard library. It takes a seq and causes it to be fully realized. If you wrap the call to remove in a doall, the reduce state will always contain a fully realized seq between each iteration, and no cascade of "I.O.U." values will build up. Instead of saving all computations for later, you do them incrementally.