As I understand walk and map both apply a function to a seq. (walk also allows the application of an outer
function post processing). However what are the idiomatic cases of using one over the other ?
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 walk
s 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))))
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With