Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

walk vs map for processing a seq

Tags:

clojure

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 ?

like image 743
murtaza52 Avatar asked Mar 08 '13 14:03

murtaza52


2 Answers

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.

like image 127
sethev Avatar answered Nov 06 '22 11:11

sethev


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))))
like image 9
A. Webb Avatar answered Nov 06 '22 10:11

A. Webb