walk vs map for processing a seq

The semantics for map are basically: apply the function to each item in the collection and return the results lazily in a sequence:

(map inc #{0 1 2}) ;outputs (when realized) (1 2 3)

Note that the input was a set but the output is a sequence.

The semantics for walk are basically: make a collection of the same type where each item has been replaced by the value of the inner function for that item, return the result of applying outer to the new collection:

(walk inc identity #{0 1 2}) ;outputs #{1 2 3}

If you look at the source code for the other functions in the walk API (http://richhickey.github.com/clojure/clojure.walk-api.html), you can see how the to make walks recursive as well (or just use those other functions).

As far as idioms go, I'm not sure. But walk is more complex so you probably should stick to map in cases where you don't need the semantics that walk offers.


Applying a function to a seq is the job of map. Use walk when you have to traverse both through and recursively into the entire structure.

Some examples of walks may be found at ClojureDocs, also available at the REPL, e.g. (user/clojuredocs clojure.walk/postwalk). Many of the examples are pedagogic and could and should be done with map or for (and sometimes reduce) in practice.

The typical use case for a walk is when you have a nested structure that you wish to process recursively. Some examples where this might be useful are the clojure.walk namespace itself, e.g. look at (source clojure.walk/keywordize-keys). [Note, if you want to process it iteratively or at will, use zippers (or tree-seq for some simpler iterative cases).]

Another example that comes to mind is interpreting parse trees:

(require '[clojure.walk :as w])

(def t [+ [* [- 6 2] [/ 9 3]] [* 2 [+ 7 8]]])

(w/postwalk #(if (and (coll? %) (fn? (first %))) (apply (first %) (next %)) %) t)
;=> 42

Perhaps useful if, e.g., replacing fn? with an allowed-fn?, etc. to evaluate an edn expression, instead of invoking the too powerful eval compiler:

(eval t) ;=> [#<core$_PLUS_ ... ] 

Oops, forms are lists, not vectors:

(def s (w/postwalk #(if (coll? %) (apply list %) %) t))
s ;=> (#<core$_PLUS_ ... )
(eval s) ;=> 42

Ah, notice here another use of a walk -- recursively changing the structure from nested vectors to nested lists.

An iterative example to meditate upon:

(require '[clojure.walk :as w])

(def s1 (range 8))
s1 ;=> (0 1 2 3 4 5 6 7)
(map inc s1)
;=> (1 2 3 4 5 6 7 8)
(w/postwalk #(if (number? %) (inc %) %) s1)
;=> (1 2 3 4 5 6 7 8)

(def s2 (partition 2 s1))
s2 ;=> ((0 1) (2 3) (4 5) (6 7))
(map (partial map inc) s2)
;=> ((1 2) (3 4) (5 6) (7 8))
(w/postwalk #(if (number? %) (inc %) %) s2)
;=> ((1 2) (3 4) (5 6) (7 8))

(def s3 (partition 2 s2))
s3 ;=> ((0 1) (2 3) (4 5) (6 7))
(map (partial map (partial map inc)) s3)
;=> (((1 2) (3 4)) ((5 6) (7 8)))
(w/postwalk #(if (number? %) (inc %) %) s3)
;=> (((1 2) (3 4)) ((5 6) (7 8)))

(def s4 (partition 2 s3))
s4 ;=> ((((0 1) (2 3)) ((4 5) (6 7))))
(map (partial map (partial map (partial map inc))) s4)
;=> ((((1 2) (3 4)) ((5 6) (7 8))))
(w/postwalk #(if (number? %) (inc %) %) s4)
;=> ((((1 2) (3 4)) ((5 6) (7 8))))

Tags:

Clojure