Det er mye å glede seg over i Clojure: pure
functions, immutability, og
REPL, for å nevne noen store ting. I dag skal vi heller
se på to bittesmå, men svært nyttige funksjoner: clojure.walk/postwalk
og
clojure.walk/prewalk
.
Du kan tenke på clojure.walk
som string/replace
for vilkårlige
datastrukturer, eller kanskje map
for nøstede data. La oss starte med et
illustrerende, om enn trivielt eksempel.
Hvis jeg har en liste med tall kan jeg lett øke dem med én ved å mappe over dem:
(map inc [1 2 3 4 5 6]) ;;=> [2 3 4 5 6 7]
Hvis dataene mine er strødd rundt i en dypere nøstet struktur kan jeg gjøre det
samme med clojure.walk
, om enn med noe mer seremoni:
(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}]}
Vi kan ikke bare hive inc
rett inn i kallet til postwalk
som vi gjorde med
map
, fordi funksjonen må håndtere alle bestanddelene i datastrukturen - og de
er ikke alle tall. En god gammaldags print gir mer innsikt i hvordan walk
fungerer:
(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}]}
Javel, hører jeg deg si. Men hva skal jeg med dette?
¶Mustache? Prøv denne barten
Mange har sikkert vært borti en eller annen form for mustache-implementasjon - altså strenger med placeholdere mellom krølleparanteser (mustachene):
(stache/render "<h1>Hei {{name}}</h1>" {:name "Christian"})
;;=> "<h1>Hei Christian</h1>"
Denne formen for templating er så nyttig at JavaScript (og sikkert andre språk) har bygget det inn i selve språket. Men hva om du ikke trengte å begrense deg til strenger?
(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"]]
Ganske sviit å slippe å bygge masse HTML inne i en streng! Det er ingenting
spesielt med :mu/stache
- det er bare et keyword jeg bruker som en markør.
Tilsvarende som {
-mustachene i streng-varianten. Denne funksjonen på 7 linjer
lener seg på de 10(!) linjene med kode som implementerer clojure.walk
, og er
allerede et lite templating-bibliotek.
¶Flere bruksområder
Ved å dra tankerekken over litt videre har Magnar og jeg laget et søtt lite i18n/theming/interpolerings-bibliotek. Det baserer seg på walk og tilbyr et snedig lite utvalg funksjonalitet som lar deg gjøre i18n (og andre ting) helt datadrevet. Og det er knappe 100 linjer med kode.
UI-ene vi lager på jobb har til og med datadrevne event handlere:
(Input
{:type :text
:value (get-in store [:temperature])
:change [[:save-in-store
[:temperature]
:event/target.value]]})
Koden vår bruker walk
til å bytte ut :event/target.value
med verdien fra
feltet når eventet fyrer. Se dette i levende, eh, døde, i Parens of the
Dead.
¶Data!
Data gjør koden enklere samtidig som den åpner for flere bruksområder.
clojure.walk
er bare ett eksempel på et lite verktøy Clojure gir deg som åpner
muligheten for mer data i langt større grad enn man først skulle tro. Neste gang
kan vi ta en prat om kompisen til walk, nemlig tree-seq
.