Ben
Ben

Reputation: 574

How to make Clojure's `while` return a value

Can someone please explain why Clojure's while macro doesn't return a value?

The documentation says "Presumes some side-effect will cause test to become false/nil." Okay, so we use swap! to modify the atom which is used in the test case. But can't we still have a return value (perhaps returning repeatedly, as in the case with loop), along with the side effects?

Looking at the source, it seems like the cause could be something to do with how recursion and macros work, but I don't know enough about the internals of that to speculate.

Example:

The following code only returns nil:

(let [a (atom 0)]
  (while (< @a 10)
    (+ 1 @a)
    (swap! a inc)))

That is, it does not return the value of (+ 1 @a), nor does it return any other value or function that is inside the while loop. If we want to get some value calculated via the while loop, we could use print, but then we can't easily use the value in some other operation. For a return value, we have to use a second atom, like this:

(let [a (atom 0)
      end (atom 0)]
  (while (< @a 10)
    (swap! end #(+ 1 %))
    (swap! a inc))
  @end)

Upvotes: 4

Views: 1068

Answers (2)

CurtainDog
CurtainDog

Reputation: 3205

Can someone please explain why Clojure's while macro doesn't return a value?

But can't we still ... along with the side effects?

Side effects are there for when you need them, but if you are too free in mixing them throughout your regular code then you are missing out on one of the huge benefits of functional programming.

In clojure.core, the 'functions' that are used for the purpose of realising some side effect are always demarcated as such and it's a good idea to follow this in your own code too.

Upvotes: 1

durka42
durka42

Reputation: 1582

Using (source while) at the REPL, we can see it is implemented like this:

(defmacro while
  "Repeatedly executes body while test expression is true. Presumes
  some side-effect will cause test to become false/nil. Returns nil"
  {:added "1.0"}
  [test & body]
  `(loop []
     (when ~test
       ~@body
       (recur))))

So, it executes the body, then checks the condition, rinse, repeat. By the time the condition is false, the most recent return value from the body is gone.

But what exactly are you trying to do? Do you really need an atom for this?

This code counts up to 10 and returns 10:

(loop [a 0]
  (if (< a 10)
    (recur (inc a))
    a))

Upvotes: 3

Related Questions