What is the difference between loop/recur and recur by itself?
Asked Answered
B

2

6

I'm having trouble finding the answer to this in the Clojure docs. I'm new to Clojure and it seems as though you can use recur in two different ways and essentially get the same result.

Example 1:

(defn my-function [num]
  (if (> num 10)
    num
    (recur (+ num 1))))

Example 2:

(defn my-function [num]
  (loop [cnt num]
    (if (> cnt 10)
      cnt
      (recur (+ cnt 1)))))

From what I can tell, these two forms seem to do the exact same thing. I understand that the reason recur is good generally is that under the right circumstances the compiler can hack together some kind of pseudo-tail-call-optimization, which I really like and would love to make use of as often as possible. So here are my questions:

  1. What is the need for using loop when recur seems to work without it?
  2. Does loop just create a "recursion scope" kind of like how let creates a mini scope?
  3. If so, can I still get the tail recursion benefits without using loop?
Birdiebirdlike answered 20/11, 2015 at 5:4 Comment(7)
With loop you can accept (and pass) arbitrary set of parameters, without loop you must pass only what function can accept.Knickerbockers
That makes sense. I believe that can answer questions 1 and 2. What about the third?Birdiebirdlike
It must be a tail call, otherwise it won't compile.Knickerbockers
Your examples are suspect. You should have if instead of when in both. I've taken the liberty of correcting them (I hope!),Stringboard
@Stringboard thanks for the code edit. As I said, I'm new to Clojure. Question for you though, when is it appropriate to use when instead of if?Birdiebirdlike
@Stringboard nevermind I figured it out. when does not use an else clause. It's essentially a do block with a condition at the beginning.Birdiebirdlike
Yes. And the cnts have no side effect, hence are quite redundant in the middle of a when form.Stringboard
K
8

Just to answer your questions one by one:

  1. loop allows you to accept and pass arbitrary parameters. Without loop you would be limited with only being able to pass only what function accepts. It would lead to heaps of tiny auxiliary functions

  2. Yep, kind of

  3. It must be a tail call and it's constrained by compiler

Knickerbockers answered 20/11, 2015 at 5:15 Comment(0)
S
5
  1. There is never any need to use loop. You can always replace it with a call to an anonymous fn form.
  2. Yes. loop acts as a let that doubles as a recursion point for recur. If a loop catches no recurs, you can replace it with a let, and vice versa.
  3. Yes. It's recur that implements tail recursion (and only tail recursion), whether it recurs to a loop or a fn form.

To illustrate (1), you can replace the loop form in example 2

  (loop [cnt num]
    (if (> cnt 10)
      cnt
      (recur (+ cnt 1))))

... with

((fn [cnt] (if (> cnt 10) cnt (recur (+ cnt 1)))) num)

... which, as you see, creates and calls an anonymous function.

You can even write loop as a macro that makes this transformation:

(defmacro loop [bindings & body]
  (let [[names values] (apply map vector (partition 2 bindings))]
    `((fn ~names ~@body) ~@values)))


(loop [i 10, ans 0]
  (case i
    0 ans
    (recur (dec i) (+ ans i))))
; 55

This may be slower than the proper clojure.core/loop.

Stringboard answered 21/11, 2015 at 17:26 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.