Fernandosg
Fernandosg

Reputation: 23

"Can only recur from tail position, compiling" error while using recur in loop

I'm very newbie with Clojure, and for practice I am trying to apply a simple algorithm for semi random numbers.

A few days ago, I read about loops in the docs of clojure, and how they work using recur, so I tried to write a loop with this lines of code:

(def numbers_semi_random
  (fn []      
    (loop[Xn 4 count 0]                    
     (while (and (not (= Xn m)) (< count m))
       (println (mod (+ (* 5.0 Xn) 7.0) m))
       (recur (mod (+ (* 5.0 Xn) 7.0) m) (inc count))    
))))

But when I execute the code, this error is displayed

CompilerException java.lang.UnsupportedOperationException: Can only recur from tail position, compiling

What's happening? recur is not in the tail of the function?

Upvotes: 2

Views: 1229

Answers (2)

Michał Marczyk
Michał Marczyk

Reputation: 84331

It seems likely that your intention would be accomplished by using when in place of while (your loop would then keep printing updated values of Xn until the condition was met), but to explain the exception:

From the perspective of the compiler, the problem is that while the while form is indeed in tail position with respect to your loop, the while macro's expansion is such that nothing in the while's body is in tail position with respect to the while form. (Were the body in tail position w.r.t. the while form, it would also be in tail position w.r.t. the enclosing loop.)

The reason this is so is that while expands to a loop expression and supplies its own recur after the user-provided body:

(while test
  body...)

;; expands to

(loop []
  (when test
    body...
    (recur)))

So, if you supply a recur as part of the body of while, what you get is

(loop []
  (when test
    ...
    (recur ...) ; from user code
    (recur)))

Here the user-supplied (recur ...) is not in tail position with respect to its immediately enclosing loop, and this is what the compiler complains about.

Upvotes: 7

noisesmith
noisesmith

Reputation: 20194

The tail position is the last form that needs to be evaluated before leaving scope. When using loop, the recur statement must be able to start fresh with new bindings, no further calculations from the previous loop left to be done.

In your code, you have a while block in the tail position of the loop. The place where you try to call recur is not in the loop's tail position (though it is in the tail of its tail).

Perhaps you intended for the recur call to be outside the while? But the comparison of loop bindings Xn and count to the global variable m in the test for while makes me think there is more to be untangled here. How many nested loops do you want here?

Upvotes: 3

Related Questions