03 lutego 2012

Clojure na kanwie zmiany cen w sklepach

Na twitterze napisałem, że może być dzisiaj ciężko posiedzieć przy Clojure, a tu podczas dyskusji na Warszawa JUG pojawiła się niezwykle interesująca propozycja zmiany planów! (proszę nie sugerować się tematem wątku, bo niezwykle szybko podryfował w innym kierunku)

Wystarczyło przespać się z tematem i rozwiązanie samo przyszło. Nie jest to specjalnie czysto funkcyjne podejście do problemu, ale ma pewne zaczątki funkcyjne, więc pozwala mi sądzić, że zaczynam czuć temat programowania funkcyjnego z Clojure.

Poniżej wycinek dyskusji, w którym przedstawiam moje funkcyjne rozwiązanie do zmiany cen w sklepie. Tym samym dziękuję całej społeczności Warszawa JUG, w szczególności Kubie Nabrdalikowi i Irkowi Matysiewiczowi za sprowokowanie mnie do bardziej wytężonej pracy umysłowej!

Korzystając z okazji, chciałbym zaprosić wszystkich zainteresowanych moimi doświadczeniami funkcyjnymi z Clojure oraz samym językiem na konferencje 33rd Degree w Krakowie w dniach 19-21.03.2012 oraz 4 Developers w Poznaniu w dniu 18.04.2012 na moje publiczne wystąpienia w temacie. Będzie duuużo dyskusji i rzeczywistego poruszania dotykanych problemów mentalnych przy rozpracowywaniu paradygmatu funkcyjnego z Clojure. Musisz tam być, jeśli temat Clojure choć w części Cię zaintrygował i akceptujesz moje, potencjalnie błędne rozumienie sprawy.

Wciąż czekam na potwierdzenie mojego udziału w konferencjach SFI w Krakowie i GeeCON w Poznaniu. Jeśli nie chcesz czytać na blogu, ani zawitać do stolicy, zaproś mnie do siebie, tj. przekonaj organizatorów wspomnianych konferencji do zaakceptowania mojego tematu wystąpienia.

To wypełnia mój polski harmonogram wokół publicznych wynużeń o Clojure i planuję w ten sposób przygotować się do wyjścia poza ramy konferencji polskich. Marzy mi się najbardziej EuroClojure w Londynie i Jazoon w Zurichu. To byłoby niezwykłe podsumowanie pracy z Clojure!

Pomożecie?

A teraz pora na wspomniany wycinek dyskusji o programowaniu funkcyjnym na forum Warszawa JUG.

2012/2/3 Jakub Nabrdalik <jakubn@gmail.com>:
> Na ziemi, developerzy robią software
> spełniający wymagania klienta. Z tych kilku sklepów które zrobiłem, w
> prawie wszystkich (w przeciwieństwie do systemów
> magazynowo-sprzedażowych/ERP, które robiłem), klient życzył sobie by
> towar nie był rezerwowany przy wkładaniu do koszyka i by cenę można
> było zmieniać w trakcie godzin pracy i by jej efekty były
> natychmiastowe (obejmowały także właśnie kupujących). Jeden wyjątek
> jaki miałem, wiosny nie czyni.

A teraz moje podejście do sprawy (podejście bardziej teoretyczne niżpraktyczne):

Klient: Chcę mieć możliwość aktualizacji ceny w trakcie transakcji (przez transakcję rozumiemy ciąg następujących po sobie czynności - od
wzięcia towaru z półki do podejścia do kasy)

Ja: Dobrze. Czy w trakcie wykonywania poszczególnych kroków powinienem informować świat zewnętrzny o potencjalnym zapotrzebowaniu na towar
(wysyłanie komunikatów)? (wersja dla nas, technicznych: czy są jeszcze nietransakcyjne operacje, których wycofanie nie będzie możliwe
korzystając z monitora transakcji? - tutaj transakcja ma znaczenie techniczne)

Klient: Nie

W tym momencie nie zakładam jeszcze, że będzie to Java, która da mi setPrice(...), albo Clojure, który zrobi to inaczej, np. {:cena ...}
(to miałem na myśli pisząc o patrzeniu na problemy przez pryzmat dostępnych narzędzi - będąc bardziej dosłowny - olewam narzędzia na tę
chwilę). Koncentruję się na wymaganiach.

I dalej rozmowa...staram się być upierdliwy^H^H^Hdociekliwy.

Ja: Co w sytuacji, kiedy podchodząc do kasy klient widzi różnicę między ceną na/przy towarze, a kasową/systemową? Chyba daje mu to
prawo do pozwania Was?

Klient: ???

Załóźmy, że klient mówi, że olewa to, co klient sobie myśli, albo, że w większości przypadków nie ma to znaczenia, bo będziemy się martwić
na miejscu.

Wtedy ja, mając tę informację, przy wystąpieniu takiej sytuacji obsługuję to jako sytuację wyjątkową (nie piszę o wyjątkach w Javie) i w razie zgłoszenia uwagi przez klienta...już w kodzie...podmieniam jego towar na nowy z nową ceną (tzn. jest to stary produkt z nową
ceną).

Sytuacja podobna jest do sytuacji, w której wszyscy wiedzą jaka jest wartość cyfry 8, ale akceptują jej zmianę, bo...cóż takie są realia
aka wymagania klienta. Cóż, ponownie, akceptuję to i jak to obsłużyłbym w Clojure:

(def basket (list)) ; koszyk jest pusty

;; towar
(def milk {:name :milk :price 10})

;; klient bierze towar z półki
(def basket (conj basket milk))

;; załóżmy, że teraz cena towaru się zmienia
(def milk {:name :milk :price 50})

;; klient ma wciąż starą wersję, bo struktury są niezmienne, stałe
;; My wiemy i chcemy, aby zmiana ceny musiała pociągać za sobą zmianę w systemie
;; najlepszą opcją, jest odłączenie ceny od towaru i wybranie jej z kasy (odłączamy atrybut cena od fizycznego towaru)
;; nasze rozumowanie wyżej jest niepoprawne
;; jeszcze raz

;; czyścimy koszyk
(def basket '())

;; zależy nam na strukturze, która ma kontrolowany dostęp (współbieżnie), ale odczyt ma być nieblokujący
;; Clojure STM przychodzi z pomocą, w końcu to malutka biblioteka do programowania współbieżnego
;; mamy do dyspozycji ref, atom i agent - http://clojure.org/atoms

(def milk-price (atom 10))

;; towar poprawniej
(def milk {:name :milk :price milk-price})

;; jaka jest teraz cena mleka w kasie?
@milk-price

;; a ta na etykiecie, na półce?
@(:price milk)

;; są równe
(= @milk-price @(:price milk))

;; wkładamy mleko do koszyka
(def basket (conj basket milk))

;; sprawdźmy cenę mleka w koszyku (idziemy do czytnika)
;; tutaj wychodzi, że możnaby pomyśleć o koszyku-mapie - identyfikator produktu i ich lista
;; wybaczcie niedbałość
(map deref (map :price (filter #(= (:name % :milk)) basket)))

;; w naszym przypadku możnaby krócej - duuuuże "skrzywienie" tematu
(def milk-price-in-basket (:price (first basket)))

;; są równe
(= @milk-price @milk-price-in-basket)

;; ZMIANA CENY w kasach
;; o jeden w górę (inc - to +1)
(swap! milk-price inc)

;; od tej pory wszystkie wątki aka klienci oraz kasy widzą to samo
;; to jest przykład uproszczenia współbieżności i cecha Clojure
;; sprawdźmy

@milk-price-in-basket

;; jeszcze podbijamy cenę
(swap! milk-price inc)

;; i jeszcze raz sprawdzmy
@milk-price-in-basket

;; koniec - Ctrl-C

Podsumowanie: All problems in computer science can be solved by another level of indirection [1]

[1] http://en.wikipedia.org/wiki/Abstraction_layer

Czekam na uwagi. Chciałbym tym samym podziękować wszystkim zaangażowanym w dyskusję za przedstawienie bardzo ciekawego problemu
do rozwiązania, który zamierzam wykorzystać w moich prezentacjach programowania funkcyjnego z Clojure. Jak się uda pierwsze będzie
podczas SFI w Krakowie 8-10.03.2012, ale spóźnionym był znacznie z rejestracją tematu, więc może być ciężko. Ale ciii, nie zapeszajmy :)