user14579348
user14579348

Reputation:

Clojure Loop can only recur from tail position

i want to create 500 circles but add them only when the new circle is not collide with an existing.

so here is my loop

  (let [total-circles 500
        circles (loop [n 0 {:keys [x y d] :as circle} (create-circle) circles ()]
                  (cond
                    (>= n total-circles) circles

                    (if (not (does-circle-have-a-collision circles circle))
                      (recur (inc n) (create-circle) (conj circles circle))
                      (recur n (create-circle) circles))

                    :else (recur (inc n) (create-circle) circles)))])

the error message is

Syntax error (UnsupportedOperationException) compiling recur at (sketch/dynamic.clj:82:21). Can only recur from tail position

What is wrong with my code?

Upvotes: 1

Views: 188

Answers (2)

wevrem
wevrem

Reputation: 413

cond takes clauses in pairs, specifically test/expr pairs. That means cond should always have an even number of forms inside it, but yours does not. Your (if ...) form is sitting in the position of a test without a subsequent expr. If we abbreviate things to see the structure, it looks like this:

(cond
  ;; test    ;; expr
  (>= ...)   circles
  (if ...)   !!!missing!!!
  :else      (recur ...))

One way to fix that is to pull out the test from your if but drop the if, like below. (Note: I rearranged your existing logic without testing it, so not sure if this will actually work, but the structure is correct. Also I put the test/expr pairs on separate lines and indented the exprs so they stand out.)

(cond
  ;; test
    ;; expr
  (>= n total-circles) 
    circles
  (does-circle-have-a-collision circles circle) 
    (recur n (create-circle) circles)
  :else 
    (recur (inc n) (create-circle) (conj circles circle)))

In the above, calling recur from any expr clause is okay.

You're keeping track of bindings in your loop/recur that you probably don't need. For example, you don't need to track n when you can just check the count of your circles collection. Let's assume you have a function

(defn add-if-no-overlap [coll c] ...)

that returns coll with c added if it doesn't overlap with any circle already in coll, otherwise it returns coll. Then you could generate your 500 non-overlapping circles like so:

(defn generate-circles [total]
  (loop [circles ()]
    (if (< (count circles) total)
      (recur (add-if-no-overlap circles (create-circle)))
      circles)))

(generate-circles 500)

Upvotes: 2

amalloy
amalloy

Reputation: 92147

Your cond is badly structured. The structure should be

(cond if1 then1
      if2 then2
      if3 then3)

But you have

(cond if1 then1
      ???mystery stuff???
      if2 then2)

Restructure your mystery stuff to be shaped the way that cond expects.

Upvotes: 1

Related Questions