08 listopada 2010

Clojure, wyrażenia regularne i ćwiartowanie

Logo ClojureIleż to razy obiecywałem sobie, że przysiądę nad wyrażeniami regularnymi. Pamiętam, jak proste było ich użycie w Groovy, a teraz, kiedy spędzam część mojego czasu z Clojure, nie jest inaczej - ponownie łatwizna. Zgoda, nie jest łatwo, jeśli nie wiadomo, jak się je konstruuje, ale jest prościej niż w Javie (co miało być potworzone, już jest gotowe do użycia). Dzisiaj miałem 2 bliższe spotkania z wyrażeniami regularnymi i oba były olśniewające.

Potrzebowałem odszukać definicji fn* w Clojure. Wszedłem na kanał #clojure na IRC i w niecałą minutę, może dwie, miałem swoją odpowiedź - fn* to specjalny symbol w Clojure (zainteresowanych źródłami odsyłam do klasy clojure.lang.Compiler).

Kiedy poszukiwałem fn* z grep wiedziałem, że muszę wyłączyć specjalność gwiazdki, więc do poszukiwań użyłem grep "fn\*" src/clj/clojure/core.clj. Prościzna. Czego jednak nie wiedziałem, to że w vi gwiazdka jest również specjalna (w końcu vi to wizualny sed, a ten wyrażenia regularne traktuje podobnie jak grep). Jakież było moje zdumienie, kiedy szukając w vi ciągu znaków "fn*" skakałem początkowo po wyrazach for, software, found, i takie tam. Dopiero wtedy uzmysłowiłem sobie, że gwiazdka oznacza 0 lub więcej wystąpień i fn* pasuje do wymienionych idealnie, bo vi traktuje gwiazdkę specjalnie. Wystarczyło ponownie odszukać "fn\*".

Jakby tego było mało, chwilę później na kanale @learnclojure na twitterze, trafiłem na cudowny kawałek kodu z...wyrażeniem regularnym! Czyż to nie podwójnie olśniewające?!
(defn vectorize-date-str [ds]
  (let [[date m d y] (re-matches #"(\d{1,2})\/(\d{1,2})\/(\d{4})" ds)]
    [y m d]))

(vectorize-date-str "12/02/1975")
;=> ["1975" "12" "02]
Funkcja vectorize-date-str opiera się na dwóch użytecznych funkcjonalnościach wbudowanych w język Clojure - "ćwiartowanie"* parametrów (ang. destructuring) oraz funkcji re-matches. Obie konstrukcje dzielą parametr wejściowy, którym jest ciąg znaków reprezentujący datę w formacie mm/dd/YYYY na listę rok, miesiąc, dzień.

Przyznaję się bez bicia, że propozycja rozwiązania tego problemu z użyciem wyrażeń regularnych byłaby ostatnia w zaproponowanych, jeśli w ogóle pojawiłaby się (!) Już przekonałem się do "ćwiartowania" parametrów (więcej w Special Forms/(let [bindings* ] exprs*)), jako wstępnego przygotowania parametrów wejściowych (i swego rodzaju kontraktu między klientem funkcji, a nią samą), ale wyrażenia regularne wciąż były przeze mnie traktowane po macoszemu. Od dzisiaj koniec z tym!

[*] Propozycje tłumaczenia destructuring mile widziane. Proszę o coś bardziej wyrafinowanego niż destrukturyzacja.