På Norsk.
You can think of clojure.walk
as string/replace
for arbitrary data
structures, or maybe as map
for nested data. Let’s start with an illustrative,
if trivial, example.
If I have a list of numbers, I can easily increment them by one by mapping over
the list:
(map inc [1 2 3 4 5 6]) ;;=> [2 3 4 5 6 7]
If my data is spread out in a deeper nested structure, I can do the same thing
with clojure.walk
, albeit with a bit more ceremony:
(require '[clojure.walk :as walk])
(def data
{:name "Christian"
:hands [{:fingers 5}
{:fingers 5}]
:legs [{:toes 5}
{:toes 5}]})
(walk/postwalk
(fn [x]
(if (number? x)
(inc x)
x))
data)
;;=>
{:name "Christian"
:hands [{:fingers 6}
{:fingers 6}]
:legs [{:toes 6}
{:toes 6}]}
We can’t just toss inc
straight into the call to postwalk
like we did with
map
, because the function needs to handle all the elements in the data
structure — and they’re not all numbers. A good old-fashioned print gives more
insight into how walk
works:
(def data
{:name "Christian"
:hands [{:fingers 5}
{:fingers 5}]})
(walk/postwalk
(fn [x]
(prn x)
x)
data)
;;=>
;; :name
;; "Christian"
;; [:name "Christian"]
;; :hands
;; :fingers
;; 5
;; [:fingers 5]
;; {:fingers 5}
;; :fingers
;; 5
;; [:fingers 5]
;; {:fingers 5}
;; [{:fingers 5} {:fingers 5}]
;; [:hands [{:fingers 5} {:fingers 5}]]
;; {:name "Christian", :hands [{:fingers 5} {:fingers 5}]}
Alright. But what are we supposed to do with this?
Mustache?
Many have probably come across some form of a
mustache implementation — that is, strings with
placeholders inside curly braces (the moustaches):
(stache/render "<h1>Hei {{name}}</h1>" {:name "Christian"})
;;=> "<h1>Hei Christian</h1>"
This form of templating is so useful that JavaScript (and probably other
languages) have built it into the language itself. But what if you didn’t have
to limit yourself to strings?
(defn render [template data]
(walk/postwalk
(fn [x]
(if (and (vector? x) (= :mu/stache (first x)))
(get data (second x))
x))
template))
(render
[:div
[:h1 {:style {:color "red"}}
"Hello" [:mu/stache :name]]]
{:name "Christian"})
;;=>
[:div
[:h1 {:style {:color "red"}}
"Hello" "Christian"]]
Pretty sweet to avoid building a bunch of HTML inside a string! There’s nothing
special about :mu/stache
— it’s just a keyword I use as a marker, similar to
the {
mustaches in the string version. This 7-line function leans on the 10(!)
lines of code that implement clojure.walk
, and is already a small templating
library.
More Use Cases
By taking this line of thinking a bit further, Magnar
and I made a neat little i18n/theming/interpolation
library. It’s based on walk
and provides a
handy feature set that lets you do i18n (and other things) in a fully
data-driven way. And it’s barely 100 lines of code.
The UIs we build at work even have data-driven event
handlers:
(Input
{:type :text
:value (get-in store [:temperature])
:change [[:save-in-store
[:temperature]
:event/target.value]]})
Our code uses walk
to replace :event/target.value
with the value from the
field when the event fires. You can see this in action in Parens of the
Dead.
Data!
Data makes code simpler while also enabling more use cases. clojure.walk
is
just one example of a small tool Clojure gives you that allows for more
data-driven solutions than you might initially think. Next time, we’ll have a
chat about walk
’s buddy: tree-seq
.