Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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?
like image 975
rescuecreative Avatar asked Nov 20 '15 05:11

rescuecreative


2 Answers

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

like image 173
zerkms Avatar answered Nov 16 '22 20:11

zerkms


  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.

like image 4
Thumbnail Avatar answered Nov 16 '22 19:11

Thumbnail