Reputation: 1245
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
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