1

I'm just building up a function at the REPL and ran into this.
I define a symbol S and give it a value:

(def S '(FRUIT COLORS (YELLOW GREEN) SKIN (EDIBLE INEDIBLE)))

I want, eventually, a function that takes the first entry in the parameter list and any and all subsequent parameter pairs and applies them to the first entry. I never got that far in my coding. I want to use a loop / recur construct (should I?), and here's how far I got in the REPL:

(loop [KV# (rest S)]
    (if (empty? KV#)
        nil
        (
            (pprint S, (first KV#), (second KV#))
            (recur (rest (rest KV#)))
        )
    )
)

I get a "can only recur from tail position" compiler error.
After looking everywhere about this including 7 or 8 articles in Stack Overflow, I can only ask: Huh?!
I'm new at this. If recur isn't in the tail position, could someone please explain to me why?
Something to do with 'if' statement syntax? GAHH! Clojure's not for the weak! Thank you.

LionelGoulet
  • 557
  • 2
  • 13
  • 3
    My Clojure is rusty, but this code looks like it will fail at runtime. You need a `do` in the `()` that's wrapping the calls to `pprint` and `recur` (`(do (pprint S, (first KV#), (second KV#)) (recur (rest (rest KV#))))`, or else you'll attempt to treat the return of `pprint` as a callable. That will likely fix the problem too. Currently, `recur` is an argument to a function, which means it won't be the last thing to execute in the function (the function using it's value will be). – Carcigenicate Feb 08 '21 at 16:36
  • Consider the following in a possibly more familiarish mein: `factorial(n) { if (n == 1) { return 1; } return n * factorial(n - 1); }`. Tail position means "the last thing done", is the recursive call, which is the last element in the function body, in tail position? No! The last thing done is the *multiplication of n and the recursive call*. If it's part of an expression like the factorial example or an aggregate `return [a, b, someRecursiveCall(c)];` it's not a tail call. It's not clear what you're trying to do in your call, but recur is clearly nested inside something else. – Jared Smith Feb 08 '21 at 17:46
  • @AlanThompson: this could be cited as a duplicate of [this question](https://stackoverflow.com/questions/10864172/clojure-can-only-recur-from-tail-position) or [this question](https://stackoverflow.com/questions/7813497/clojure-what-exactly-is-tail-position-for-recur/7813537), but as it's not actually a tail position problem but a minor-but-common syntax mistake I've posted an answer. – Bob Jarvis - Слава Україні Feb 08 '21 at 20:35
  • 1
    Independent from the tail call problem: most times you can avoid loop/recur by using coljure's sequence manipulating functions.. `(doseq [[k v] (partition 2 (rest S))] (println S k v))` would do the same thing you're trying to do, while keeping the code concise and for that less error prone. – leetwinski Feb 09 '21 at 10:13

1 Answers1

6

You've made one of my favorite mistakes in Clojure - you've tried to use parentheses to group code. You need to use a (do ...) form to group forms together, as in:

(loop [KV# (rest S)]
    (if (empty? KV#)
        nil  ; then
        (do  ; else
          (pprint S, (first KV#), (second KV#))
          (recur (rest (rest KV#)))
        )
    )
)

This gets rid of the "recur not in tail position" problem, but still fails - an arity exception on pprint - but I'll leave that for you to solve.

How did I spot this? My rule is that any time I find two left-parens together I immediately assume I've made a mistake and I need to figure out what I did wrong. In this case it was a little harder to spot because the left-parens were separated by intervening white space - but still, from the view of the lexical scanner they're adjacent to one another. So you just have to learn to think like a lexical scanner. :-)

  • 1
    Bob J: It works! Thank you, especially for the "2 left parens" warning. – LionelGoulet Feb 09 '21 at 14:59
  • Carcigenicate: you had it right out of the gate. – LionelGoulet Feb 09 '21 at 15:00
  • leetwinski: I *LOVE* it! partition! I guess I don't have enough miles on my Clojure odometer to have every intrinsic function in my vocabulary. I wondered if there was just such a function in Clojure. Kewl! – LionelGoulet Feb 09 '21 at 15:06
  • @LionelGoulet, just so you know, Bob Jarvis's the two-left-parens rule is an excellent rule of thumb, but there *are* appropriate uses in which two left parens appear. They are unlikely to come up when you're first learning Clojure, though (unless you're working through a tutorial (such as *The Little Schemer*, for Scheme) that introduces some slightly deeper functional programming ideas very early). I'd also recommend learning correct Lisp/Clojure code formatting, and working with an editor that knows how to format Clojure. Once you get used to it, it helps you see certain errors. – Mars Feb 13 '21 at 23:25