Gregory
Gregory

Reputation: 1245

Clojure local-variables

I want to create a function (thunk) that will return successive elements in a list. What is the best way to do this? I wrote this code based on an apparently flawed understanding of how local variables in clojure work:

   (defn reader-for [commands]
      (with-local-vars
        [stream commands]
        (fn [] 
          (let
            [r (var-get stream)]
            (if (empty? r)
              nil
              (let
                [cur (first r)
                 _   (var-set stream (rest r))]
                cur))))))

In this code I get:

#<CompilerException java.lang.IllegalStateException: Var null/null is unbound. (Chapel.clj:1)>

which seems to suggest that with-local-vars is dynamically scoped. Is that true? Is there any lexically scoped alternative? Thanks for any help.

Upvotes: 1

Views: 4955

Answers (1)

Alex Taggart
Alex Taggart

Reputation: 7825

If you require mutable state, use one of the clojure reference types:

user=> (defn reader-for [coll]
         (let [a (atom coll)] 
           (fn []
             (let [x (first @a)]
               (swap! a next)
               x))))
#'user/reader-for
user=> (def f (reader-for [1 2 3]))
#'user/f
user=> (f)
1
user=> (f)
2
user=> (f)
3
user=> (f)
nil

Also, let is for lexical scoping, binding is for dynamic scoping.

Edit: the thread-safe version as pointed out by Alan.

(defn reader-for [coll]
  (let [r (ref coll)] 
   #(dosync
      (let [x (first @r)]
        (alter r next)
        x))))

And just for fun, a thread-safe version with atoms (don't do this):

(defn reader-for [coll]
  (let [a (atom coll)] 
    (fn []
      (let [ret (atom nil)]
        (swap! a (fn [[x & xs]]
                   (compare-and-set! ret nil x)
                   xs))
        @ret))))

Upvotes: 4

Related Questions