Et lite Clojure-triks til å bli glad av

Magnar om Clojure og Funksjonell programmering

Publisert 02.10.2019

Det viktigste og beste med Clojure er pure functions og immutability, men i dag skal jeg skrive om noe ganske annet. Jeg skal skrive om en småting som gjør meg glad når jeg koder.

Det er bare å glede seg

Her er en liten kodesnutt:

(defn add-new-zombie [zombies]
  (conj zombies (new-zombie)))

(update game :zombies add-new-zombie)

Funksjonen update tar i mot et map game, en nøkkel :zombies i det mappet, og en funksjon add-new-zombie som skal brukes til å oppdatere verdien bak den nøkkelen.

(conj legger en verdi til i en liste)

Det er naturligvis ille dust at det finnes en funksjon add-new-zombie. Vi vil heller ha små byggeklosser enn små funksjoner.

Dermed kan vi dra koden inn via en anonym funksjon:

(update game :zombies #(conj % (new-zombie)))

Hmfr. «Jeg trodde liksom det skulle være så lite syntax i denne JVM-lispen din, Magnar?» sier du kanskje. «Hva i alle verdens land og rike er disse skigardene og prosenttegnene?»

Jeg er enig. Jeg skjemmes litt. Vi kan skrive en skikkelig funksjonsliteral istedet:

(update game :zombies (fn [zombies] (conj zombies (new-zombie))))

Som du kanskje ser så er % en slags anaforisk parameter – spesiell syntaks for å vinne kodegolfturneringer. Det er ikke det som gjør meg glad når jeg koder.

Her er det

Den vanlige formen av update ser slik ut:

(update map key function)

Det som gjør meg glad er den alternative formen. Den tar varargs, og ser slik ut:

(update map key function args...)

Disse ekstra argumentene får bli med inn i funksjonen. Og trikset er: verdien som skal oppdateres sendes inn først! … og så resten av argumentene etterpå.

Slik blir det:

(update game :zombies conj (new-zombie))

Det leser: «Oppdater spillet sine zombier ved å legge til en ny zombie.» Svit.

Og ikke bare det!

Dette trikset funker også med update-in og swap!. Sistnevnte oppdaterer Clojure sine atomer, et slags fengsel for muterbar state.

Vi trenger altså ikke skrive:

(swap! game-atom (fn [game] (update game :zombies (fn [zombies] (conj zombies (new-zombie))))))

Det skulle tatt seg ut. Så mange parenteser, da gett. Vi vet at vi allerede kan forenkle det til:

(swap! game-atom (fn [game] (update game :zombies conj (new-zombie))))

Alternativt kan vi forenkle det til:

(swap! game-atom update :zombies (fn [zombies] (conj zombies (new-zombie))))

Men her er det aller mest nydelige. Ettersom disse komponerer helt lekkert, koker det hele ned til:

(swap! game-atom update :zombies conj (new-zombie))

Det er nesten så jeg får tårer i øyekroken.

Diskusjon

Vi diskuterer gjerne hvor enn du finner oss. Ta kontakt!

Mer fra bloggen