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.