Wersja Clojure 1.3.0-alpha2 dostępna jest na jego stronie domowej (sekcja Downloads).
Podniesienie wersji Clojure nie wystarczy do korzystania z monad (w wersji 1.2).
devmac:~ jacek$ clj Clojure 1.3.0-alpha2 user=> (use 'clojure.contrib.monads) CompilerException java.lang.ClassNotFoundException: clojure.set, compiling:(clojure/contrib/accumulators.clj:121)Konieczne jest pobranie standalone-1.3.0-alpha2.jar (z repo clojure.contrib) i umieszczenie w CLASSPATH dla Clojure REPL. Od tej chwili można cieszyć się dobrodziejstwem inwentarza.
Oczywiście w projektach zalecane jest skorzystanie z narzędzia w stylu Apache Maven, np. dla Clojure byłby nim Leiningen, a później należy postępować zgodnie z clojure-contrib 1.3.0-alpha2 deployed to build.clojure.org.
Po tych zmianach uruchamiamy REPL i wczytujemy przestrzeń c.c.monads, w której definiowane są monady.
devmac:~ jacek$ clj Clojure 1.3.0-alpha2 user=> (use 'clojure.contrib.monads) nilRozgrzewkę mamy za sobą.
Wracając do tematu przewodniego, tym razem nie będzie, do czego służą monady programistom czy matematykom, ale jak się je definiuje w Clojure i czym są w tym języku (ich wewnętrzną reprezentacją). Mam nieodparte wrażenie, że wiele zostało powiedziane o monadach i ten obszar został już dostatecznie zagospodarowany (zainteresowanym polecam przeczytać artykuły, które mam za sobą nt. monad ze znacznikiem "monads" na delicious.com).
Już wiemy (patrz poprzednie wpisy w kategorii clojure), że matematycznie i praktycznie monada to trójka składająca się z konstruktora typu - obliczenia, z którym związane są dwie, obowiązkowe funkcje - w terminologii Clojure będą to m-bind i m-result.
Poznawanie monad w Clojure opieram w dużej mierze na czytaniu artykułów, ale zauważam postęp w zrozumieniu ich sensu, kiedy poza materiałem literackim, uzupełniam go o przegląd źródeł c.c.monads z ich testami (dostępne w repo Gita - clojure-contrib/modules/monads).
Makro monad
Pierwsza konstrukcja tworzenia monad w Clojure to makro monad, które definiuje monadę jako mapę nazw funkcji i ich implementacji (coś ala klasa w Javie). W monadzie-mapie znajdziemy wskazanie na dwie, obowiązkowe funkcje m-result i m-bind oraz opcjonalne m-zero i m-plus.
user=> (monad [m-result identity m-bind (fn [mv f] (f mv)) ]) {:m-plus :clojure.contrib.monads/undefined, :m-zero :clojure.contrib.monads/undefined, :m-bind #<user$eval919$m_bind__920 user$eval919$m_bind__920@45570f5c>, :m-result #<core$identity clojure.core$identity@20773d03>}W ten sposób zdefiniowaliśmy monadę. Tylko, co można z nią zrobić? Nic. W Javie mogłoby to odpowiadać konstrukcji new PewnaKlasa() bez przypisania jej do zmiennej (nie wliczając skutków ubocznych, co jest możliwe, ale nierekomendowane, np. wykonanie statycznej metody, albo stworzenie wątku). W Clojure nie ma zmiennych (to jest koncept języka imperatywnego), a jedynie symbole (koncept języka funkcyjnego).
Innymi słowy, monada w Clojure jest niczym innym jak mapą składającą się z nazw funkcji w postaci kluczy :m-plus, :m-zero, :m-bind i :m-result z ich implementacją (jeśli podana na wejściu).
Dobrze byłoby przypisać nazwę takiej strukturze, np. za pomocą (def nazwa (monad ...)).
user=> (def moja-monada (monad [m-result identity m-bind (fn [mv f] (f mv)) ])) #'user/moja-monada user=> moja-monada {:m-plus :clojure.contrib.monads/undefined, :m-zero :clojure.contrib.monads/undefined, :m-bind #<user$fn__923$m_bind__924 user$fn__923$m_bind__924@54c707c1>, :m-result #<core$identity clojure.core$identity@20773d03>} user=> (type moja-monada) clojure.lang.PersistentArrayMapc.c.monads dostarcza już takiego makro - defmonad.
user=> (doc defmonad) ------------------------- clojure.contrib.monads/defmonad ([name doc-string operations] [name operations]) Macro Define a named monad by defining the monad operations. The definitions are written like bindings to the monad operations m-bind and m-result (required) and m-zero and m-plus (optional). nil user=> (defmonad moja-monada-2 [m-result identity m-bind (fn [mv f] (f mv)) ]) #'user/moja-monada-2 user=> moja-monada-2 {:m-plus :clojure.contrib.monads/undefined, :m-zero :clojure.contrib.monads/undefined, :m-bind #<user$fn__927$m_bind__928 user$fn__927$m_bind__928@67b14530>, :m-result #<core$identity clojure.core$identity@20773d03>}
Makro with-monad
Kolejnym makro w c.c.monads jest with-monad. Dzięki niemu operacja monadyczna zostaje związana z konkretną implementacją w danej monadzie.
Weźmy za przykład monady: maybe-m, sequence-m oraz state-m. Każda z nich musi dostarczać dwie metody - m-bind oraz m-result. Każda z nich musi działać na z góry ustalonej strukturze obliczeniowej. Zobaczmy.
user=> (with-monad maybe-m (m-result 1)) 1 user=> (with-monad sequence-m (m-result 1)) (1) user=> (with-monad state-m (m-result 1)) #<monads$fn__774$m_result_state__775$fn__776 clojure.contrib.monads$fn__774$m_result_state__775$fn__776@65cb048e>W przypadku funkcji m-result jej wynikiem jest zwrócenie wartości monadycznej dla podanej na wejściu - dla maybe-m będzie to po prostu podana wartość, sequence-m zwróci listę jednoelementową z podaną wartością, a state-m zwróci funkcję, która na wejściu wymaga podania stanu (środowiska, w którym będzie działało ciało funkcji "stanowej") i w wyniku jej wykonania dostaniemy parę - wartość, która została podana na wejściu m-result oraz aktualny stan (nim może być cokolwiek i najczęściej jest kolejna mapa z przypisaniami symbol - wartość, jak zmienne w Javie). Sprawdźmy.
user=> (def f-stanowa (with-monad state-m (m-result 1))) #'user/f-stanowa user=> (f-stanowa 5) [1 5] user=> (f-stanowa {:a 5 :b 7}) [1 {:a 5, :b 7}]I teraz najlepsze: gdyby sobie wyobrazić (a może po prostu przypomnieć), że każdy program (w dowolnym języku) wykonuje się na pewnym stanie, który moglibyśmy reprezentować jako mapę - zmienna-wartość, to z monadą state-m możemy wykonać program napisany w języku funkcyjnym bez pojęcia stanu, emulując stan monadą. Tworzymy środowisko - stan początkowy - i każde wykonanie funkcji w jego ramach będzie dotyczyło jedynie tego stanu. Zamiast przekazywać stan z funkcji do funkcji (przez ich szeregowanie) można skorzystać z monady state-m i zjąć sobie ten kłopot z barków, wykonując funkcje tak, jakby ten stan po prostu był. Zdecydowanie upraszcza testowanie i zrównoleglanie obliczeń, bo zakłada się, że funkcje nie mają skutków ubocznych i działają w zamkniętej przestrzeni. To było dla mnie niezwykle odkrywcze w swojej prostocie.
Wystarczy tych pieśni na dzisiaj. Jutro kolejna porcja moich znalezisk monadycznych. Pytania i uwagi mile widziane. Jako podsumowanie można oczekiwać prezentacji na Warszawa JUG. Ach, następne będzie 9. listopada z Cezarym Bartoszukiem, który przedstawi temat "Przegląd języków programowania i ich funkcjonalności".
Brak komentarzy:
Prześlij komentarz