Redefining a let'd variable in Clojure loop

后端 未结 4 1330
难免孤独
难免孤独 2021-02-01 02:00

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)
            


        
相关标签:
4条回答
  • 2021-02-01 02:41

    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 讨论(0)
  • 2021-02-01 02:55

    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.

    0 讨论(0)
  • 2021-02-01 03:00

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

    Relevant part:

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

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

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

    0 讨论(0)
  • 2021-02-01 03:03

    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.

    0 讨论(0)
提交回复
热议问题