21.1.12

next rest loop recur

I just ran across this example usage of the loop/recur paradigm in clojure, and its use of the"next" function, and it kinda threw me off, at first.  

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.


The importance of this is in the implementation of loop/recur - by using next/rest, the loop/recur functionality gives us side-effect-free, high-performance iteration through a data structure.





1 comment:

  1. Nice. It's always frustrating when two languages use the same words with very different semantics.

    ReplyDelete