Here is the "official" clojure introduction of loop/recur :
;;Classic iteration use case : taking 2 linear collections and
;;zips them up into a map. The ideal LISPy way to do this is
;;tail-recursion.
(defn my-zipmap [keys vals]
(loop [my-map {}
my-keys (seq keys)
my-vals (seq vals)]
(if (and my-keys my-vals)
(recur (assoc my-map (first my-keys) (first my-vals))
(next my-keys)
(next my-vals)) my-map)))
The thing that got me worried was the "next" function. It scared me - because I'm a java programmer - and it immediately triggered the ugliness of iteration in java :
//The iterator is statefull - its state changes on each iteration, and the call to next() advances its state to
// the next index. This is slightly better then the for loop , since for loops have additional side effects... // but its still statefull.
while(i.hasNext())
{
doSomething(i.next());
}
HOWEVER just like the "for" in clojure is not a "for loop" - so the "next" in clojure is neither an iterator next() function.
Rather, next returns the "end" of a list :
user=> (next [1 2 3])
(2 3)
That is, there is no state modification here - next is simply giving you a NEW data structure which "looks" the way the rest of a list should look you complete iterating its first element. The advantage of this, of course, is the fact that we never have any side effects - calling
user=> (let [a [1 2 3]] a (next a))
(2 3)
user=> (let [a [1 2 3]] a (next a) (next a) )
(2 3)
Thus, the next() function in clojure is indeed stateless, we can call it as many times as we want and it returns the exact same thing, everytime. I think its an unfortunate alias - "next" implies that there might be some state being modified - SO I looked into it.
It turns out that there is also a "rest" function, which does the same thing. At least, in the scenario. I went ahead and did some RTFM - and here is what I saw :
-------------------------
clojure.core/next
([coll])
Returns a seq of the items after the first. Calls seq on its
argument. If there are no more items, returns nil.
nil
user=> (doc rest)
-------------------------
clojure.core/rest
([coll])
Returns a possibly empty seq of the items after the first. Calls seq on its
argument.
nil
So - I guess these basically do the same thing EXCEPT when the list is empty - if we call "next" - we get nil [there is no "next"]. However, we call "rest" on a list 1 or 0 element list, we simply get an empty list.
Nice. It's always frustrating when two languages use the same words with very different semantics.
ReplyDelete