[ACCEPTED]-Redefining a let'd variable in Clojure loop-function

Accepted answer
Score: 50

def defines a toplevel var, even if you use 18 it in a function or inner loop of some code. What 17 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 16 not mine.) You don't need mutable state 15 for your example here; you could use loop and 14 recur.

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

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

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

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

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

(You don't 11 need a do; while wraps its body in an explicit 10 one for you.) If you really, really wanted to do this 9 with vars you'd have to do something horrible 8 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 7 not idiomatic Clojure at all. To use Clojure 6 effectively you should try to stop thinking 5 in terms of mutable state. It will definitely 4 drive you crazy trying to write Clojure 3 code in a non-functional style. After a 2 while you may find it to be a pleasant surprise 1 how seldom you actually need mutable variables.

Score: 13

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

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

There's 5 nothing stopping you from doing:

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

If you want 4 a thread-local settable "place" you can 3 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 2 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 1 is pretty unidiomatic.

Score: 6

If you think that having mutable local variables 29 in pure functions would be a nice convenient 28 feature that does no harm, because the function 27 still remains pure, you might be interested 26 in this mailing list discussion where Rich 25 Hickey explains his reasons for removing 24 them from the language. Why not mutable locals?

Relevant part:

If 23 locals were variables, i.e. mutable, then 22 closures could close over mutable state, and, given 21 that closures can escape (without some extra prohibition 20 on same), the result would be thread-unsafe. And 19 people would certainly do so, e.g. closure-based 18 pseudo-objects. The result would be a 17 huge hole in Clojure's approach.

Without 16 mutable locals, people are forced to use 15 recur, a functional looping construct. While 14 this may seem strange at first, it is just 13 as succinct as loops with mutation, and 12 the resulting patterns can be reused 11 elsewhere in Clojure, i.e. recur, reduce, alter, commute 10 etc are all (logically) very similar. Even 9 though I could detect and prevent mutating 8 closures from escaping, I decided to keep 7 it this way for consistency. Even in the 6 smallest context, non-mutating loops are 5 easier to understand and debug than mutating 4 ones. In any case, Vars are available 3 for use when appropriate.

The majority of 2 the subsequent posts concerns implementing 1 a with-local-vars macro ;)

Score: 3

You could more idiomatically use iterate and take-while instead,

user> (->> 128
           (iterate #(/ % 2))
           (take-while (partial < 1)))

(128 64 32 16 8 4 2)
user>

0

More Related questions