tags:

views:

836

answers:

2

OK. I've been tinkering with Clojure and I continually run into the same problem. Let's take this little fragment of code:

(let [x 128]
  (while (> x 1)
    (do
      (println x)
      (def x (/ x 2)))))

Now I expect this to print out a sequence starting with 128 as so:

128
64
32
16
8
4
2

Instead, it's an infinite loop, printing 128 over and over. Clearly my intended side effect isn't working.

So how am I supposed to redefine the value of x in a loop like this? I realize this may not be Lisp like (I could use an anonymous function that recurses on it's self, perhaps), but if I don't figure out how to set variable like this, I'm going to go mad.

My other guess would be to use set!, but that gives "Invalid assignment target", since I'm not in a binding form.

Please, enlighten me on how this is supposed to work.

+13  A: 

def defines a toplevel var, even if you use it in a function or inner loop of some code. What you get in let are not vars. Per the documentation for let:

Locals created with let are not variables. Once created their values never change!

(Emphasis not mine.) You don't need mutable state for your example here; you could use loop and recur.

(loop [x 128]
  (when (> x 1)
    (println x)
    (recur (/ x 2))))

If you wanted to be fancy you could avoid the explicit loop entirely.

(let [xs (take-while #(> % 1) (iterate #(/ % 2) 128))]
  (doseq [x xs] (println x)))

If you really wanted to use mutable state, an atom might work.

(let [x (atom 128)]
  (while (> @x 1)
    (println @x)
    (swap! x #(/ %1 2))))

(You don't need a do; while wraps its body in an explicit one for you.) If you really, really wanted to do this with vars you'd have to do something horrible like this.

(with-local-vars [x 128]
  (while (> (var-get x) 1)
    (println (var-get x))
    (var-set x (/ (var-get x) 2))))

But this is very ugly and it's not idiomatic Clojure at all. To use Clojure effectively you should try to stop thinking in terms of mutable state. It will definitely drive you crazy trying to write Clojure code in a non-functional style. After a while you may find it to be a pleasant surprise how seldom you actually need mutable variables.

Brian Carper
Thanks. I realize my way wasn't Lispy, as side effects are frowned upon. I was hacking through something (a Project Euler problem) and couldn't get that simple test case to work, proving I didn't understand something. Thanks for the help. I forgot loop could hold recur, that works very cleanly (without the extra function doing recursion).
MBCook
Side effects are Lispy depending which Lisp you're looking at. In Common Lisp you'd get away with (loop for x = 128 then (/ x 2) while (> x 1) do (print x)). But side effects aren't Clojurish.
Brian Carper
+3  A: 

Vars (that's what you get when you "def" something) aren't meant to be reassigned (but can be):

user=> (def k 1)
#'user/k
user=> k
1

There's nothing stopping you from doing:

user=> (def k 2)
#'user/k
user=> k
2

If you want a thread-local settable "place" you can use "binding" and "set!":

user=> (def j) ; this var is still unbound (no value)
#'user/j
user=> j
java.lang.IllegalStateException: Var user/j is unbound. (NO_SOURCE_FILE:0)
user=> (binding [j 0] j)
0

So then you can write a loop like this:

user=> (binding [j 0]
         (while (< j 10)
           (println j)
           (set! j (inc j))))
0
1
2
3
4
5
6
7
8
9
nil

But I think this is pretty unidiomatic.

twopoint718