niedziela, 26 października 2014

CodeRetreat 2014 - nowe propozycje

To jest post promujący CodeRetreat 2014 w Łodzi. Sponsorem wydarzenia w 2014 roku jest firma Symphony Teleca. Spotkanie odbędzie się 15 listopada zaś link do meetupa i zapisów jest tutaj -> link do zapisów.

Jeśli tylko nie zapomniałem odblokować zapisów to już można się dodać. W tym roku Marek chciał zaprosić jakieś gwiazdy zagraniczne do poprowadzenia CR jednakowoż coś tam komuś wypadło i na razie pozostała moja skromna osoba do pomocy przy tym evencie. Jeśli komuś znudził się ten sam prowadzący 3 rok z rzędu może śmiało napisać na contact@juglodz.pl z własną propozycją ;)

Zmiany

Na ostatnim spotkaniu JUGa kilka osób narzekało - "nie tylko nie znowu Game of Life". Gra życie jako taka jest dosyć ciekawa pod względem edukacyjnym dlatego chce ją zostawić ale dla zwiększenia różnorodności i nauki świeżych technik nadszedł czas na jakieś nowe ograniczenia. Oczywiście dla tych, którzy chcą pozostaną standardowe ćwiczenia.

Jakiś czas napisałem artykuły o nowych podejściach do rozwiązania problemu komórek:

Ponieważ nie każdy lubi Scalę a również powyższe rozwiązania mogą być zbyt egzotyczne dla osób codziennie programujących w Javie - więc poniżej przyjrzymy się kilku patentom zaprezentowanym w Javie8.

Immutable i referential transparency

Pierwsze ograniczenie : obiekty muszą być Immutable (naprawdę nie wiem czy słowo "niemutowalne" w ogóle istnieje w słowniku polskim czy też to coś w stylu "komitować") i do tego należy zachować "referential transparency" (i znowu tłumaczenie tego zwrotu na polski może przynieść więcej szkody niż pożytku)

Spostrzeżenia :

  • Komórki nie przechowują w zasadzie żadnego stanu
  • Nie ma konieczności tworzyć za każdym razem nowej żywej/martwej komórki
abstract class Cell{
 abstract Cell evolve(int liveNeighbours);
}

class LiveCell extends Cell{
 @Override
 Cell evolve(int liveNeighbours) {
  if(liveNeighbours ==2  || liveNeighbours ==3){
   return liveCell();
  }else{
   return new DeadCell();
  }
 }
 
 static Cell liveCell(){
  return new LiveCell();
 }
} 

class DeadCell extends Cell{
 @Override
 Cell evolve(int liveNeighbours) {
  if(liveNeighbours ==3){
   return new LiveCell();
  }else{
   return new DeadCell();
  }
 }
        static Cell deadCell(){
  return new DeadCell();
 }
}
Ponieważ komórka jako obiekt nie może być raz żywa a raz martwa to nie ma potrzeby przechowywania informacji o stanie każdej komórki i możemy pokusić się o symulację nieskończonej planszy przy pomocy zwykłej mapy.
Map<Coordinates, Cell> board=new HashMap<>();
board.put(coordinates(1, 1), liveCell());
board.put(coordinates(1, 2), liveCell());
board.put(coordinates(1, 3), liveCell());
//evolve
Map<Coordinates, Cell> nextPhaseBoard=new HashMap<>();
//
Martwa komórka jest reprezentowana jako prosty "brak komórki" w mapie. I teraz możemy wykorzystać bardzo fajny patent z Javy 8, który został dodany do Mapy.
//calculations
Cell cellAt22 = board.getOrDefault(coordinates(2, 2), deadCell());
...

Bardziej Funkcyjnie

Na początek przyjmiemy ograniczenie - "Nie można tworzyć nowych klas" (Może z wyjątkiem klasy, która symuluje tuple/tupla bo to jest wygodne a w Javie8 jeszcze tego nie ma). Komórki będą zwykłym enumem co z jednej strony ogranicza rozszerzalność ale z drugiej ułatwia ćwiczenie.

Na początek nieskomplikowana implementacja coby mieć miłe wrażenie pracy z funkcjami :
Function<Integer, Cell> evolveLife = liveNeighbours -> {
  if (liveNeighbours == 2 || liveNeighbours == 3)
   return LIVE;
  else
   return DEAD;
 };
 
 Function<Integer, Cell> evolveDead = liveNeighbours -> {
  if (liveNeighbours == 3)
   return LIVE;
  else
   return DEAD;
 };

 BiFunction<Cell,Integer, Cell> evolve = (cell,liveNeighbours) -> {
  if (cell == LIVE) {
   return evolveLife.apply(liveNeighbours);
  } else {
   return evolveDead.apply(liveNeighbours);
  }
 };

 enum Cell {
  DEAD, LIVE
 }
W powyższej próbce kody uwazny czytelnik zauważy funkcję "evolve" i dwie pomocnicze funkcje dedykowane dla konkretnych typów komórek - generalnie powinno łatwo dać się to testować. No i możemy rzucić okiem jak wyglądać będzie wywołanie:
System.out.println(evolveLife.apply(3));
  System.out.println(evolveLife.apply(4));
  System.out.println(evolveDead.apply(3));
  System.out.println(evolveDead.apply(4));
  System.out.println(evolve.apply(LIVE,4));
  System.out.println(evolve.apply(LIVE,3));

Currying

Kolejne ograniczenie to "maksymalnie jeden parametr na funkcję". W tym przypadku nie trzeba używać BiFunction (a tak w ogóle to Java8 zdaje się nie ma czegoś takiego jak funkcja z 3 lub więcej parametrami).

Function<Cell,Function<Integer, Cell>> evolve = cell->liveNeighbours -> {
  if (cell == LIVE) {
   return evolveLife.apply(liveNeighbours);
  } else {
   return evolveDead.apply(liveNeighbours);
  }
 };

//wywołanie
System.out.println(evolve.apply(LIVE).apply(4));
System.out.println(evolve.apply(LIVE).apply(3));

W dużym uproszczeniu cały patent z jednym parametrem pozwala nam wstrzykiwać "kontekst" uzyskując funkcję jedno-argumentową przygotowaną do rozwiązania specyficznego problemu.
Function<Coordinates, List<Coordinates>> findNeigbhoursCoordinates=coordinates->{
  return asList(//jakies tam obliczenia);
 };
 
 Function<Map<Coordinates, Cell>, Function<Coordinates,Integer>> countNeighbours=board->coordinates->{
  return (int)findNeigbhoursCoordinates.apply(coordinates)
  .stream()
  .map(c->board.getOrDefault(c, DEAD))
  .filter(cell->cell==LIVE)
  .count();
 };
 
 Function<Integer,Function<Cell, Cell>> evolve = liveNeighbours->cell-> {
  if (cell == LIVE) {
   return evolveLife.apply(liveNeighbours);
  } else {
   return evolveDead.apply(liveNeighbours);
  }
 };
I działać to mogłoby tak :
void test(){
  Map<Coordinates, Cell> boardCurrentPhase=new HashMap<>();
  Map<Coordinates, Cell> boardNextPhase=new HashMap<>();
  List<Coordinates> potentialCellsWithChangeState=new LinkedList<>();
  
//wstrzykujemy planszę z danej fazy gry
  Function<Coordinates, Integer> currentPhaseNeighbours = countNeighbours.apply(boardCurrentPhase);
  
  potentialCellsWithChangeState.forEach(c->{
   Integer n = currentPhaseNeighbours.apply(c);
   Cell cell = boardCurrentPhase.getOrDefault(c, DEAD);
//tutaj akurat nie ma konkretnego zysku z rozdzielenia parametrów ale nadal zróbmy to dla ćwiczeń
   Cell evolvedCell = evolve.apply(n).apply(cell);
   if(evolvedCell==LIVE) boardNextPhase.put(c,evolvedCell);
  });
  
 }

Powyższy przykład być może nie oddaje jakoś specjalnie wszystkich zalet konstrukcji [jeden argument,zwracana funkcja] ale na tę chwilę nic lepszego nie przychodzi mi do głowy. Jeśli ktoś jest ciekaw owych zalet to niech zerknie na link -> what-is-the-advantage-of-currying

W naszym kodzie można jeszcze zrobić coś takiego :

Map<Cell, Function<Integer, Cell>> handlers=new HashMap<Cell, Function<Integer, Cell>>(){{
  put(LIVE,evolveLife);
  put(DEAD,evolveDead);
 }};
 
 Function<Map<Cell, Function<Integer, Cell>>,Function<Integer,Function<Cell, Cell>>> evolveGeneral = 
   handlers->liveNeighbours->cell-> handlers.get(cell).apply(liveNeighbours);
 
 Function<Integer, Function<Cell, Cell>> evolve = evolveGeneral.apply(handlers);

Teraz możemy niezależnie modyfikować funkcje obsługi ewolucji poszczególnych komórek ale kosztem potworka w systemie typów (czyli to co tam jest po lewo)

Małe podsumowanie

  • Funkcja evolve nic nie wie o tym na jakieś planszy toczy się gra czyli uzyskaliśmy tzw. "information hiding"
  • Funkcja countNeighbours być może wie trochę za dużo - tutaj można by jeszcze oddzielić funkcjonalność zliczania określonych typów komórek od pobierania ich z planszy(Może zwrócić stream?).
  • evolveLife i evolveDead to proste funkcje do testowania
  • Można jeszcze podzielić to co dzieje się w metodzie test
  • Teoretycznie po zmianie w countNeighbours ze stream na parallelStream wszystko powinno działać
Czy można to zrobić lepiej? Na pewno - rok temu próbowałem napisać coś takiego w scali i wyszła masakra :) - generalnie za każdym razem idzie to coraz lepiej. Na tym właśnie polega nauka. Na CR nikt nikogo nie ocenia i tez po to usuwamy zawsze kod - by nie bać się oceny. Strach przed oceną to chyba jeden z największym hamulców w nauce i samorozwoju ogólnie.

Bardziej obiektowo

Nie koniecznie trzeba rozwiązywać CR funkcyjnie - po prostu można i z Javą 8 jest łatwiej (niż z Javą7). Można też pokusić się o rozwiązanie CR według niektórych bardziej obiektowo niż na klasach - a mianowicie na aktorach. Może coś w tym temacie uda mi się jeszcze opisać przed 15 listopada.

niedziela, 12 października 2014

Subiektywny poziom szczęścia i lepsza percepcja

Artykuł rozpoczniemy od czegoś zupełnie z nim nie związanego. Niedawno Erik Meijer zareklamował swój kurs programowania funkcyjnego na stronie edukacyjnej o której istnieniu nawet nie wiedziałem : https://www.edx.org/. (A sam kurs jest tutaj : link do kursu) Dzięki temu szczęśliwemu splotowi zdarzeń jakże szczęśliwie udało mi się na tej samej stronce natrafić na inny szczęśliwy kurs : The Science of Happiness

Wbrew pozorom nie jest to kurs przyrządzania kompotu czy też gotowania grzybów lecz całkiem naukowe podejście do tego o co chodzi w ogólnie pojętym poczuciu szczęście, jak ludzie je odczuwają oraz co dla programistów może być najciekawsze : jak ono wpływa na naszą percepcję (No a percepcja w trakcie programowania to jest ważna).

Czy to się może przydać?

Według badań bardzo - ale co to za badania(standardowo amerykańskich naukowców)? Otóż Ludziom z różnym acz nierównym poziomem subiektywnej percepcji szczęścia prezentowano pewne obrazki i sprawdzano "jak szeroka" była ich percepcja - czyli np. czy zauważyli jakieś dziwne szczegóły w tle lub na peryferiach sceny. Robi się przy tym odpowiednio dużą ilość pomiarów aby mieć pewność, że konkretne zachowanie nie wynika ze skłonności indywidualnych.

Albo lepszy patent - popularnie pożądane "myślenie out-of-the-box". Ludzie oceniają obrazki składające się z trójkątów i kwadratów i w zależności od poziomu "poczucia szczęścia" rozpoznają więcej podobieństw w szczegółach (tu i tu są trójkąty) lub - przy większym poczuciu szczęścia - podobieństwa w ułożeniu kształtów - gdzie "kształt" czy "ułożenie" raczej idą w stronę operowania na abstrakcjach - czyli lekko wychodzi się ze wstępnie zdefiniowanego schematu.

A sytuacja gdzie ktoś skupiał się bardziej na szczegółach też niekoniecznie musi mieć swoje plusy. Generalnie ogólny "stan niepokoju" wyzwala reakcję walcz lub uciekaj, które przydaje w razie walki z dzikami lub żulami w nocnym ale niekoniecznie do szukania bugów.

Jak na razie zainteresowanych dokładnymi badaniami mogę odesłać do prac dwóch pań, które zajmują się tą dziedziną.

I zamiast linka filmik z opisem badań -

Adaptacja hedonistyczna

Na wstępie ważny komunikat aby ktoś błędnie nie zrozumiał poniższych wywodów. Stawianie sobie wyzwań, bycie ambitnym, posiadanie pasji która popycha nas do kolejnych działań, walki o lepsze życie i nie pozwala stać w miejscu to dobre rzeczy. W dalszych akapitach chodzi jedynie o subiektywne odczucie tego co się dzieje i o zastąpienie poczucia subiektywnie wygenerowanego poczucia nieszczęścia czymś lepszym

Standardowy dzień : Wstajesz i myślisz - o jak zajebiście mam łóżko i w ogóle mam jakieś mieszkanie - jak cudownie. Idziesz do lodówki i pełen szczęścia myśli - "no jak wyśmienicie, że mam jakieś jedzenie". Podążasz na przystanek i w twoje głowie rozbrzmiewa pieśń fascynacji - "czy to nie cudowne, że mamy w ogóle komunikację miejską?". Jedziesz i z radością spoglądasz w okno ze świadomością, że przecież nie trzeba się denerwować bo nikt nie będzie do ciebie strzelać.

Mechanizmem odpowiedzialnym za to, że poniższy opis się w zasadzie u nikogo nie wydarzy jest - Adaptacja hedonistyczna czyli zwykłe przyzwyczajenie się do naszego obecnego otoczenia.

Pomyślimy jakie jest najgorsze położenie w jakim możemy się znaleźć? Może zaczniemy od autora Viktor Frankl i jego książki Man's Search for Meaning. On opisuje tam swoje przeżycia - zwłaszcza wewnętrzne - z obozu koncentracyjnego w trakcie drugiej wojny światowej. Wydaje mi się, że to właśnie w tej książce są opisy ekstazy jaką przeżywał autor gdy znalazł gdzieś na ziemi kawałek nadającego się do jedzenie zagubionego ziemniaka.

Może też być Etiopia czy też inny kraj gdzie ludzie na co dzień umierają z głodu. Czy wstając rano cieszysz się, że w okół ciebie nie ma strażników obozu a w lodówce jest jedzenie i do tego bieżąca woda w kiblu (a no i, że w ogóle jest jakiś kibel!). Raczej nie. Być może twoje życie jest "nieszczęśliwe" bo samochód ma już 7 lat i znowu w te wakacje nie udało ci się wyjechać dalej niż do Egiptu :((( I do tego ku**a robisz ciągle w Javie 6!!

Perspektywa

Jeśli np. życie przeciętnego Polaka nie jest wypełnione radością z faktu, iż wiedzie mu się znacznie lepiej niż przeciętnemu mieszkańcowi Etiopii to ciekawe czy ten efekt idzie jeszcze dalej i dalej...?

  • Mieszkaniec najbiedniejszych obszarów Afryki
    • "Wstaję codziennie rano i od samego początku, od najwcześniejszych godzin odczuwam głód a świeżej wody nie widziałem od miesięcy, dlaczego jaa, dlaczegooo?!?! "
  • Średnia klasa - Europa wchodnia
    • "Wstaję codziennie rano i od samego rana sąsiad kuje ściany, dlaczego jaa, dlaczegooo?!?! "
  • Średnia klasa - Europa zachodnia
    • "Czekałem na ten wyjazd od dwóch miesięcy i akurat wczoraj popsuła się moja terenówka, dlaczego jaa, dlaczegooo?!?! "
    Właściciel średniej firmy
    • "Cholera jasna byłą okazja zarobić 40 baniek ale za szybko się zgodziłem i mam tylko 30, fak,fak,fak,fak,fak,fak?!?! "
    Właściciel ogromnej firmy globalnej
    • "Cholera jasna i znowu Bil Gejts jest na okładce Tajmsa, co robię źle, co robię nie tak?!?!?! I jeszcze akcje poszły 0,1% w dół - nieszczęścia chodzą parami!!Dlaczego, dlaczegooo?!?!? "

Oczywiście to tylko przypuszczenia - być może ludzie z 4 ostatnich pozycji wstają rano i z pełną piersią mówią "ależ jestem niesamowicie szczęśliwy, że moje życie nie jest tak przejebane jak (przynajmniej) przeciętnego Polaka:D"

Lekarstwo

Istnieje bardzo pomocna technika - Cognitive reframing, która doskonale nadaje się do odzyskania poczucia szczęścia w "jedynie subiektywnie strasznej sytuacji". Potrzebna będzie jedynie dobra wyobraźnia, która pozwoli odczuć scenkę wszystkimi zmysłami. Trzeba się wczuć w wymyśloną sytuację a następnie przez odczucie różnic pomiędzy tym gdzie jesteśmy a gdzie moglibyśmy być - dojść do przekonania (popartego wszystkimi zmysłami), że w sumie to mamy dobrze :)

Scena 1

Mieszkasz gdzieś na północy Iraku. Budzi cię strasznie głośny huk. Po kilku sekundach gdy gdy twoja świadomość w końcu przestawia się ze snu w stan aktywny orientujesz się ku swemu przerażeniu, że twoja wioska jest otoczona przez odziały państwa islamskiego. Nie myśląc długo wybiegasz na podwórko - twoje serce bije 200 uderzeń na minutę. Niestety zauważa cię jeden z żołnierzy i niecelnym strzałem trafia w lewe kolano. Osuwasz się w bólu na ziemię. Żołnierz podbiega do ciebie skracając dystans tak aby mieć pewność, ze kolejna kula trafi w głowę. Czujesz zimno bijące od ziemi i przed oczyma przelatuje ci całe życie. Naqle staje się coś dziwnego - wszystko w okół zamiera. Żołnierz, który chciał cię zaatakować stoi w bezruchu. Niczego nie słychać. I wtedy tak jak na film S-F zagina się czasoprzestrzeń i pojawia się wróżka (lub wróżek jak kto woli) - taka mała latająca z różdżką - słyszysz głos. "- Dam ci wybór ale wybór nie będzie łatwy. Możesz zostać tu gdzie jesteś albo obudzisz się gdzieś w europie wschodniej w firmie informatycznej. Będziesz miał mieszkanie i samochód ale słuchaj jest tutaj haczyk
- co?
- otóż dzień w którym się obudzisz nie będzie zwykłym dniem. Jest to bardzo zły dzień - dzisiaj padły wam serwery, mieliście downtime i SLA poszło się walić i właśnie zaczyna się miting z menadżerami.
- co mnie to interesuje!?!?! zabierz mnie stąd
"

Jak będziesz się czuł gdy już obudzisz się gdzieś w Europie wschodniej? Jaki będzie twój poziom szczęścia gdy napastników z karabinami zastąpi spotkanie z managerami? I znowu nie chodzi o to, że awaria serwerów to coś dobrego - raczej jeśli to tylko możliwe warto jej unikać. Chodzi jedynie o to co się dzieje w tym momencie w tobie. Uspokoisz się - będziesz miał czystszy umysł i sprawniej rozwiążesz problem, nie będzie wrzodów i nadwagi wywołanej stresem.

Scena 2

Mieszkasz gdzieś na wschodzie Ukrainy. Ze smutkiem spoglądasz na gruzy twojego domu w którym spędziłeś całe swoje życie. Wózek z kilkoma ubraniami i zwinięty tobołek jedzenia - to wszystko co ci zostało gdy oddziały separatystów zbombardowały ostatniej nocy okoliczne zabudowania. Nie spałeś od 40 godzin, czujesz się niezwykle zmęczony, przed tobą długa droga chociaż w zasadzie nie wiesz gdzie będziesz podążał. Nie wiesz nawet czy dotrzesz tam żywy. Wtem z okolicznej drogi dobiega mrożący krew w żyłach odgłos gąsienic czołgu - są bliżej niż myśleliście... Błyskawicznie odgłos milknie, przyroda wokół ciebie wydaje się nie ruszać. Kolor stają się wyblakłe. Czujesz obecność czegoś bliżej nieokreślonego - nie wiadomo skąd przemawia do ciebie głos o bardzo niskim tonie "- Dam ci wybór ale wybór nie będzie łatwy. Możesz zostać tu gdzie jesteś albo obudzisz się gdzieś w europie wschodniej - będziesz miał całkiem wygodne życie w firmie informatycznej ale słuchaj jest tutaj haczyk
- co?
- Otóż stary słuchaj nie uwierzysz, kupiłeś płytki za 500 złotych a ludzie z ekipy, którą wynająłeś krzywo je położyli i trzeba wszystko zrywać.
- co mnie to interesuje!?!?! zabierz mnie stąd
"

Jak będziesz się czuł gdy już obudzisz się gdzieś w Europie wschodniej? Jaki będzie twój poziom szczęścia gdy spaloną wioskę zastąpisz krzywo położonymi płytkami? I znowu nie chodzi o to, że 500 złotych w plecy to coś dobrego - jeśli to tylko możliwe należy tego unikać. Chodzi jedynie o to co się dzieje w tym momencie w tobie. Uspokoisz się - będziesz miał czystszy umysł i sprawniej rozwiążesz problem, nie będzie wrzodów i nadwagi wywołanej stresem.

I trochę trucizny

Jak sprzedać produkt? Klient ma potrzebę, której jest świadomy. Ponieważ jest ona niezaspokojona to jest przez to mniej szczęśliwy - produkt zostaje sprzedany a klient jest teraz szczęśliwszy gdyż potrzebę zaspokoił.

Co gdy klient nie ma potrzeby? Albo trzeba mu ją uświadomić - na przykład, że istnieje narzędzie X, które pomoże lepiej robić pracę Y. Dopóki klient nie wiedział o narzędziu X nie mógł być nieszczęśliwy, że go nie ma (choć mógł być nieszczęśliwy, że wykonuje pracę Y). Jeśli np. obcinanie gałęzi wcale nie było takie straszne ale teraz z nowym sekatoremX można to robić przyjemniej to marketing musi się zająć tym aby aby uczucie niewygody spowodowanej używaniem starego sekatora było tak duże, że klient kupi nowy. Teoretycznie marketing musi sprawić aby klient poczuł się bardziej nieszczęśliwy w swojej obecnej sytuacji. (za wyjątkiem oczywiście sytuacji gdzie Y naprawdę warto zaspokoić nowym produktem bez zbędnej ingerencji zespołu z marketingu - np. zbiera pszczelego miodu nago niekoniecznie musi być przyjemnym zajęciem - dobry skafander nie potrzebuje tutaj reklamy).

Inna sytuacja to gdy nie ma absolutnie żadnej potrzeby by używać produktu X albo ta potrzeba jest marginalna. Ostatnio na przykład bombardują mnie jakieś reklamy szamponów na youtube. Koleś wchodzi na imprezę, laski go ignorują - później wali szampon na dłoń rozprowadza po włosach. Te mu się ca chwilę puszą i wyginają aż nagle staje się duszą towarzystwa. No ciekawe co sobie myśli łysy z brazzers jak ogląda tę reklamę :D Tak czy inaczej marketingu po raz kolejny próbuje wygenerować poczucie niespełnienia, niezaspokojonej potrzeby - nieszczęścia, którą zaspokoi produkt. Temat starałem się poruszyć dawno temu - Higiena psychologiczna - to było ponad dwa lata temu także, część przemyśleń może być już nieaktualna.

Zakońćzenie

Na polskim w technikum uczyli, że trzeba pisać zakończenie do wypracowań - a ja mam to w dupie. Będę czuł się szczęśliwy bez zakończenia czego i Tobie życzę drogi czytelniku.

piątek, 3 października 2014

PlayFramework warsztaty - REST - część 2

To jest dalszy ciąg lekcji o Jsonie i webserwisach -- A tutaj jest część 1

Pomimo iż materiału wydawało się być dużo to jednak udało się zrobić całość w 2,5 godziny (Między innymi dlatego, ze już nie chce mi się z pizzą kombinować i nie ma przerwy - ale zawsze ostrzegam - ludzie weźcie kanapki! (Albo odżywki - przyp. red.)).

Prąd i sala się same nie opłaciły to ten tego :
A poniżej wszyscy bez żadnego zewnętrznego przymuszenia promieniują wewnętrznym szczęściem i radością :

A tutaj są linki do poprzednich części :

Wywołanie

Do tego ćwiczenia zostawiamy włączoną aplikacyjkę z poprzedniej części - to będzie apiserwer , zaś to co powstanie tutaj to taki apiclient.

By obydwa serwery się nie pogryzły ten drugi uruchamiamy na innym porcie w ten oto wygodny sposób :

~run 8000

I wołamy :
def webserwis = Action.async {
    import scala.concurrent.ExecutionContext.Implicits.global
    val futureResult=WS.url("http://localhost:9000/list").get
    futureResult.map{response=>
     Ok("otrzymalem : "+response.json)
    }
  }

I to w zasadzie tyle w wariancie podstawowym - powinno śmigać. Trzeba uczestnikom wytłumaczyć tutaj co to jest to "async" i nadmienić, że z wywołania WS dostajemy

Future[Response]
. O samych Future będzie więcej za chwilę.

No i mogą pojawić się pytania o to co to jest i skąd się wziął ExecutionContext. W tym miejscu może się przydać strona z dokumentacji Playa o pulach wątków.

Wysyłanie posta z ciałem

Wygląda tak :
def dodajMeal = Action.async {
    val result = WS.url("http://localhost:9000/add").post(Json.obj("name" -> "kurczak", "calories" -> 1000))
    result.map(r => Ok("dostałem : " + r.body))
  }

Future poligon

Kiedy już mamy ogólne pojęcie o używaniu klienta Webserwisów przyjrzyjmy się bliżej temu co ono zwraca - czyli Future. Tutaj można wspomnieć jak Future jako abstrakcja ratuje nas od masy zagnieżdżonych callbacków.

Jedna ważna rzecz jeśli chodzi o zabawę Future - tutaj nie da się używać worksheet bo plik jest cały czas odświeżany i procek się pali dlatego normalnie w mainie napiszemy kod.

Najpierw zasymulujmy synchroniczne wywołanie serwisu z zamuleniem jednej sekundy

object Poligon {

  def main(args: Array[String]) {
    val result = servis1
    println("start")
    println(result)
    println("end")
  }

  def servis1 = {
    Thread.sleep(1000)
    10
  }
}

I wynik :
start
10
end

Z future


  def main(args: Array[String]) {
    val executorService = Executors.newCachedThreadPool()
    implicit val executionContext = ExecutionContext.fromExecutorService(executorService)

    val result = Future{servis1}
    println("start")
    result.onSuccess{case result=>println(result)}
    println("end")
  }
Wywołanie serwisu nastąpiło asynchronicznie więc rezultat będzie na samym końcu.
start
end
10
No i aby pokazać jakie patenty są możliwe to dajmy na to zmodyfikujemy sobie rezultat
val result = Future { servis1 }.map(_+3)
I mamy teraz :
start
end
13

dwa servisy

O a tutaj proszę jak ładnie komponujemy dwa asynchroniczne wywołania serwisów
object FuturePoligon {
  def servis1() = {
    Thread.sleep(1000)
    10
  }
  
  def servis2(arg:Int) = {
    Thread.sleep(1000)
    arg+7
  }
}

val result = Future { servis1 }.map(result1=>servis2(result1))
    println("start")
    result.onSuccess{case result=>println(result)}
    println("end")
start
end
17

Który future pierwszy

object FuturePoligon {
  def servis1() = {
    Thread.sleep(1000)
    10
  }
  
  def servis2(arg:Int) = {
    Thread.sleep(800)
    arg+7
  }
}

    val future1 = Future { servis1 }
    val future2 = Future { servis2(4) }
    val completedFuture=Future.firstCompletedOf(Seq(future1,future2))
    println("start")
    completedFuture.onSuccess{case result=>println(result)}
    println("end")

start
end
11

Obsluga faili

import scala.util.{Failure,Success}

 val futureFail = Future {
      throw new Exception("się zjebało")
    }

    futureFail.onComplete {
      case Success(value) => println(value)
      case Failure(e)     => println(e)
    }

    futureFail.onFailure {
      case e => println(e)
    }
wypisuje :
java.lang.Exception: się zjebało
java.lang.Exception: się zjebało

Zaginione strony

Ponieważ te ćwiczenia nie powstają za jednym razem ale ciągle wracam i coś poprawiam także czasem zostawiam sobie notatki na przyszłość. W tym miejscu zapisałem sobie "ćwiczenia na typach i zdjęcie z goblinsów" - o co mogło mi chodzić? Pojęcia nie mam. Ale obrazek będzie :

Co dalej?

Na pewno ciekawe będzie poćwiczenie użycia Play i Akka razem ale wcześniej - ponieważ jest zainteresowanie takimi ogólnymi warsztatami o Scali to coś zrobimy też w tym kierunku :

poniedziałek, 15 września 2014

PlayFramework warsztaty - usługi REST - tworzenie i konsumpcja

Warsztaty

Spotkanie odbędzie się 23 września we wtorek - a tutaj są wszystkie szczegóły -

I klika zdjęć z ostatnich zajęć na dowód, że te warsztaty się naprawdę odbywają :) (Bo nigdy nic nie wiadomo - mogę w tej chwili siedzieć gdzieś zakuty w fartuch w zakładzie a to wszystko może dziać się w mojej wyobraźni ale dla potrzeb marketingowych załóżmy, że na 99,999% się jednak odbywają)

Plan na zajęcia

W tej partii materiału skupiamy się na wysyłaniu JSONa z serwera do klienta. Do tego aby nie bawić się w Javascript - klientem będzie inna aplikacja Playowa, która to umożliwi nam naukę używania klienta WebSerwisów(ale o tym w drugiej części).

Cechą charakterystyczną tych zajęć będzie więcej zabawy z bibliotekami Playa samymi w sobie aniżeli z Playem samym w sobie ale oczywiście na Play samego w sobie też się znajdzie czas sam w sobie.

A tutaj są linki do poprzednich części :

Coś na start

Aby dać przedsmak tego jak wygodne jest użycie w playu JSONa - szybki przykład na start. Taki mały kodzik a jakże czytelny i ileż on robi! :

def dajJsona = Action {
    import play.api.libs.json.Json._
    val json = obj(
      "temat" -> "play i json",
      "plan"->arr("krótki przykład","ćwiczenia z biblioteką","dłuższy przykład","webserwisy")
    )
    Ok(obj("message"->json))
  }
I w odpowiedzi dostaniemy
{"message":
 {"temat":"play i json",
   "plan":["krótki przykład","ćwiczenia z biblioteką","dłuższy przykład","webserwisy"]
 }
}
I wiadomo typ "application/json"

Zabawa z JSONem

A zabawa będzie polegała na ćwiczeniach z Jsonem w worksheet. Otwieramy sobie nowy pliczek, kopiujemy to co pod spodem i wio.

//wazny imporcik
import play.api.libs.json._

val mealJson: JsValue = Json.parse("""
{
  "meal": {
    "name" : "hamburger",
    "calories" : 3000,
    "description" : "bardzo tuczące jedzenie",
    "isJunk" : true,
    "ingredient" : {
      "name" : "imitacja mięsa",
      "calories" : 2000
    }
  }
}
""")
Obiekt do ćwiczeń mamy przygotowany więc teraz poćwiczymy jak go parsować i jak po nim nawigować
// wyszukiwanie pojedynczej wartosc
 val name= mealJson \ "meal" \ "name"             //> name  : play.api.libs.json.JsValue = "hamburger"

//wyszukiwanie listy wszystkich wartosci
 val calories= mealJson \\ "calories"             //> calories  : Seq[play.api.libs.json.JsValue] = List(3000, 2000)
Następnie małe ćwiczonka na konwersję do typów Scali:
//zwykła konwersja do scali
 val nameScala= (mealJson \ "meal" \ "name").as[String]
                                                  //> nameScala  : String = hamburger
 //jakby się miało wywalić to prosimy nie o zwykły typ ale o Option
 (mealJson \ "meal" \ "nieMaMnie").asOpt[String]  //> res0: Option[String] = None
 
Jak już czytamy to i można od razy walidować
 //można od razu walidować
 val zle=(mealJson \ "meal" \ "nieMaMnie").validate[String]
                                                  //> zle  : play.api.libs.json.JsResult[String] = JsError(List((,List(ValidationE
                                                  //| rror(error.expected.jsstring,WrappedArray())))))
 zle.map(name=>"jem :"+name)                      //> res1: play.api.libs.json.JsResult[String] = JsError(List((,List(ValidationEr
                                                  //| ror(error.expected.jsstring,WrappedArray())))))
 
 val dobre=(mealJson \ "meal" \ "name").validate[String]
                                                  //> dobre  : play.api.libs.json.JsResult[String] = JsSuccess(hamburger,)
 dobre.map(name=>"jem :"+name)                    //> res2: play.api.libs.json.JsResult[String] = JsSuccess(jem :hamburger,)

Sami tworzymy JSONa

Co gdy chcemy sami stworzyć obiekt JSONa z istniejących danych? Nie trzeba oczywiście sklejać stringa gdyż istnieje bardzo wygodny i wyrazisty mechanizm do wypełnienia właśnie tego oto zadania. Także uczestnicy kursu są tutaj poproszeni o zastąpienie stringa poniższą konstrukcją i wszystko hulać powinno.

//poniższy import działa dlatego, że wcześniej mamy import "play.api.libs.json._" - fajny patent
import Json._
val mealJson=obj(
 "meal"->obj(
  "name"->"hamburger",
  "calories"->3000,
  "description"->"bardzo tuczące jedzenie",
  "isJunk" -> true,
  "ingredient"->obj("name"->"imitacja mięsa","calories"->2000)
 )
)

Automatyczne konwersje

Mamy do dyspozycji metodę "Json.toJson(...)", która konwertuje nam dany typ w JSona. Działa z automatu dla podstawowych typów Scali - aby działało dla naszych typów musimy dostarczyć przepis na konwersję. Potem robi się "implicit" dzięki czemu nie trzeba tego cały czas przekazywać do metod.

                     //> json  : play.api.libs.json.JsValue = {"name":"mięso","calories":3000}
                                                  //| } = controllers.writers$$anonfun$main$1$$anon$1@50b98ef4

I teraz różne implementacje "ingredientWrites" można sobie importować z różnych bibliotek (ale nie na raz bo się pogryzą) i w ten sposób można konfigurować, który sposób konwersji ma być użyty. Zerknijmy na bardziej rozbudowany przykład z pełną klasą "Meal"

case class Ingredient(name:String,calories:Int)
  case class Meal(name:String,calories:Int,description:String,isJunk:Boolean,i:Ingredient)
  
  implicit val ingredientWrites=new Writes[Ingredient]{
   def writes(i:Ingredient)=obj(
    "name"->i.name,
    "calories"->i.calories
   )
  }                                               //> ingredientWrites  : play.api.libs.json.Writes[controllers.writers.Ingredient
                                                  //| ]{def writes(i: controllers.writers.Ingredient): play.api.libs.json.JsObject
                                                  //| } = controllers.writers$$anonfun$main$1$$anon$1@24ef2645
  val ingredient=Ingredient("mięso",3000)         //> ingredient  : controllers.writers.Ingredient = Ingredient(mięso,3000)
  
  implicit val mealWrites=new Writes[Meal]{
   def writes(meal:Meal)=obj(
    "name"->meal.name,
    "calories"->meal.calories,
    "description"->meal.description,
    "isJunk"->meal.isJunk,
    //I tutaj łądnie wykorzystujemy wcześniej zadeklarowany "Writer" dla typu Ingredient
    "ingredient"->meal.i
   )
  }                                               //> mealWrites  : play.api.libs.json.Writes[controllers.writers.Meal]{def writes
                                                  //| (meal: controllers.writers.Meal): play.api.libs.json.JsObject} = controllers
                                                  //| .writers$$anonfun$main$1$$anon$2@347db2f9
  val meal=Meal("Hamburger",3000,"złe jedzenie",true,ingredient)
                                                  //> meal  : controllers.writers.Meal = Meal(Hamburger,3000,złe jedzenie,true,In
                                                  //| gredient(mięso,3000))
  
  val json=toJson(meal)                           //> json  : play.api.libs.json.JsValue = {"name":"Hamburger","calories":3000,"de
                                                  //| scription":"złe jedzenie","isJunk":true,"ingredient":{"name":"mięso","calo
                                                  //| ries":3000}}
A żeby było ładniej to można :
prettyPrint(json)                         //> res0: String = {
                                                  //|   "name" : "Hamburger",
                                                  //|   "calories" : 3000,
                                                  //|   "description" : "złe jedzenie",
                                                  //|   "isJunk" : true,
                                                  //|   "ingredient" : {
                                                  //|     "name" : "mięso",
                                                  //|     "calories" : 3000
                                                  //|   }
                                                  //| }

I w drugą stronę

Jak do zamiany obiektu w JSONa jest "Writes" tak do zamiany na odwyrtkę jest "Reads". Tutaj będzie trochę inaczej bo korzystamy z lekko innego api. "Writes" też można tworzyć tym innym sposobem co być może dałoby lepsze wrażenie spójności ale to mapowanie przez strzałki, którego użyliśmy powyżej jest prostsze.

//najpierw taki dziwny import bo będziemy korzystać z innego api
import play.api.libs.functional.syntax._

//i tera
implicit val ingredientReads: Reads[Ingredient] = (
  (JsPath \ "name").read[String] and
  (JsPath \ "calories").read[Int]
 )(Ingredient.apply _)                     //> ingredientReads  : play.api.libs.json.Reads[controllers.writers.Ingredient]
                                                  //|  = play.api.libs.json.Reads$$anon$8@503dbd9a

   //i wyłuskiwać naszą klasę można
  (json \ "ingredient").as[Ingredient]      //> res0: controllers.writers.Ingredient = Ingredient(mięso,3000)
  //i walidować
  (json \ "ingredient").validate[Ingredient]      //> res1: play.api.libs.json.JsResult[controllers.writers.Ingredient] = JsSucce
                                                  //| ss(Ingredient(mięso,3000),)

Tutaj walidacja była poprawna ale gdyby coś się popsuło to można tak zadziałać :
  val zlyJson=Json.obj{"zly"->"json"}             //> zlyJson  : play.api.libs.json.JsObject = {"zly":"json"}

  (zlyJson \ "ingredient").validate[Ingredient] match {
   case s:JsSuccess[Ingredient] => s"dobre ${s.get}"
   case e:JsError => s"złe ${JsError.toFlatJson(e)}"
  }                                               //> res2: String = złe {"obj.name":[{"msg":"error.path.missing","args":[]}],"o
                                                  //| bj.calories":[{"msg":"error.path.missing","args":[]}]}

Jeszcze dokładniejsza walidacja

implicit val ingredientReads: Reads[Ingredient] = (
  (JsPath \ "name").read[String](minLength[String](10)) and
  (JsPath \ "calories").read[Int](min(1000) keepAnd max(8000))
 )(Ingredient.apply _)

//I niestety "hamburger się nie załapał"
(json \ "ingredient").as[Ingredient]      //> play.api.libs.json.JsResultException: JsResultException(errors:List((/name,
                                                  //| List(ValidationError(error.minLength,WrappedArray(10))))))

Makra

I tutaj te wszystkie napisane Writery i Readery można zastąpić takim oto zestawem jednolinijkowców.

 object JsonImplicits{
   case class Ingredient(name:String,calories:Int)
   case class Meal(name:String,calories:Int,description:String,isJunk:Boolean,i:Ingredient)
   implicit val macroIngredientFormat=Json.format[Ingredient]
   implicit val macroMealFormat=Json.format[Meal]
 }
  
  import JsonImplicits._
To automatycznie generuje Writes i Reads makrami na podstawie Definicji Klas. Kiedy to testowałem to aby działało w Worksheet musiałem owe makra zamknąć w zewnętrznym obiekcie, zaimportować i wtedy trybi. Może tak to musi działać aby makra zaskoczyły a być może muszę się po prostu wyspać.

No to jakaś aplikacyjka w Playu

object Application extends Controller {
 def index = Action {
  Ok(views.html.index("Your new application is ready."))
 }

  import play.api.libs.json.Json

  implicit val mealFormat=Json.format[Meal]

  def list = Action {
    Ok(Json.toJson(MealDatabase.data))
  }
  
  def oneByName(n:String)=Action{
    MealDatabase.data.find(_.name.toLowerCase==n).map{foundMeal=>
     Ok(Json.toJson(foundMeal))
    }.getOrElse(BadRequest(s"no meal with name ${n}"))
  }
  
  def add=Action(parse.json){request=>
    val meal=request.body.as[Meal]
    MealDatabase.add(meal)
    Created(s"added ${meal.name}")
  }

}

case class Meal(name:String,calories:Int,description:Option[String])

object MealDatabase{
 var data=List(Meal("HotDog",2500,Some("Parówa w bule")),Meal("Pizza",4000,None))
 
 def add(m:Meal)=data=m::data
}
No i Testy
"Application" should {

    "return meals list" in new WithApplication{
      val jsonResult = route(FakeRequest(GET, "/list")).get
      
      contentType(jsonResult) must beSome.which(_ == "application/json")
      contentAsJson(jsonResult) \\ "name" must containAllOf(Seq(JsString("HotDog"),JsString("Pizza")))
    }
    
    "return one meal by name" in new WithApplication{
      val jsonResult = route(FakeRequest(GET, "/meal/pizza")).get
      
      contentType(jsonResult) must beSome.which(_ == "application/json")
      contentAsJson(jsonResult) \ "name" must beEqualTo(JsString("Pizza"))
    }
    
    "add new meal to list" in new WithApplication{
      val inputJson=Json.obj("name"->"pomidorowa","calories"->5000)
      val addingResult = route(FakeRequest(POST, "/add").withJsonBody(inputJson)).get
      
      status(addingResult) must beEqualTo(CREATED)
      contentAsString(addingResult)  must beEqualTo("added pomidorowa")
    }
  }

I teraz w planach jest, iż zostanie trochę czasu aby przełączyć się na naukę wywołania webserwisów... co opiszę w następnym odcinku. W sumie jak ludzie będą chcieli to można jechać JSONa przez całe spotkanie - grunt by było ciekawie bo tylko wtedy nauka ma sens. Z drugiej strony wywołania asynchroniczne i działanie na abstrakcjach, które symbolizują coś co się jeszcze nie wydarzyło i zmaterializuje się w przyszłości jest magiczne samo w sobie... i znowu wielokropek.

*   *   *

środa, 3 września 2014

Neurologia Pair Programming

"Programowanie w parze" jeszcze kilka lat temu szło (a być może wciąż tu i tam idzie) wbrew intuicyjnej wiedzy managerskiej zapożyczonej wprost z fabryki pralek. Bo gdzie jest profit jak dwa risorsy robią pracę jednego risorsa?

Z jednej strony są relacje programistów, którzy mówią iż czasem "co dwie głowy to nie jedna" a z drugiej jest jakiś irracjonalny wzór na efektywność mierzoną ilością wciśniętych klawiszy w jednostce czasu. Kiedyś nawet na JUGu w Łodzi był koleś z prezentacją jakiegoś chorego pluginu do eclipse zliczającego akcje programisty w przeciągu godziny.(Zastanawiałem się czy o tym w ogóle wspominać by jakiś managerski "mlody wilku" nie poczuł inspiracji).

Być może poniższy tekst doda trochę siły naukowej do faktu, iż czasem siedzenie we dwóch przy jednym kompie może być wnieść coś więcej niż tylko "programista razy 2"

Sieć zadaniowa - co się dzieje gdy siedzimy sami nad kompem

Gdy sami siedzimy zagłębieni w problemie aktywowana jest sieć : http://en.wikipedia.org/wiki/Task-positive_network

  • Zalety : skupiamy się na jednej konkretnej rzeczy i rozwiązujemy konkretny problem.
  • Wady : jesteśmy zamknięci na nowe pomysły i sposoby podejścia do danego problemu - a to dlatego, że w danej chwili aktywna jest tylko jedna sieć.
  • Komentarz : oczywiście mogą pojawić się głosy ludzi twierdzące, że to bez znaczenia ponieważ programista ma tylko przepisać diagram narysowany przez kogoś z bardziej egzotyczną nazwą stanowiska. Owe osoby zapytane "skoro to tylko kwestia przepisania diagramu to dlaczego nie mogą tego robić automaty" - odpowiadają pomidor

Sieć społeczna - co się dzieje gdy siedzimy w parze

Teraz powinna działać cześć sieci : http://en.wikipedia.org/wiki/Task-negative A w celu uzupełnienia informacji można też zerknąć na :
  1. http://en.wikipedia.org/wiki/Default_mode_network
  2. http://www.ncbi.nlm.nih.gov/pmc/articles/PMC3380415/ - tu jest dużo i naukowo.
  • Zalety : otwieramy się na nowe pomysły i możliwości rozwiązania danego problemu.
  • Wady : trudniej jest nam się skupić na jednej rzeczy i zanalizować konkretny problem dokładniej i dogłębniej.
  • Komentarz : oczywiście mogą pojawić się głosy ludzi twierdzące, że to bez znaczenia ponieważ programista ma tylko przepisać diagram narysowany przez kogoś z bardziej egzotyczną nazwą stanowiska. Owe osoby zapytane "skoro to tylko kwestia przepisania diagramu to dlaczego nie mogą tego robić automaty" - odpowiadają pomidor
Poniżej default mode network to zdaje się zielone i niebieskie.

Podsumowanie

Rzecz jasna nie było bezpośrednich badań nurkujących wprost do głów programistów w sytuacjach niezwykle fascynującej codzienności życia IT. Powyższy opis jest jedynie interpretacją obecnych faktów naukowych ale być może przyda się jako argument do dyskusji (jeśli w jakichś firmach takie dyskusje wciąż trwają) odnośnie produktywności programowania w parach.

Należy zerwać z błędną wizją "dwóch risorsów" przy jednym komputerze - a zaprosić nowe przekonanie, że programiści czy to pracując sami w słuchawkach czy też żywo dyskutując w N osób - pracują tak naprawdę w różnych trybach przydatnych do rozwiązywania różnych problemów.

oczywiście mogą pojawić się głosy ludzi twierdzące, że to bez znaczenia ponieważ programista ma tylko przepisać diagram narysowany przez kogoś z bardziej egzotyczną nazwą stanowiska. Owe osoby zapytane "skoro to tylko kwestia przepisania diagramu to dlaczego nie mogą tego robić automaty" - odpowiadają pomidor

Informacje do tego artykułu napotkałem w nowym kursie na courserze - Inspiring Leadership through Emotional Intelligence. Na początku myślałem, że to będzie standardowy badziew managerski w stylu "klep ludzi po ramieniu, mów good job, pamiętaj o urodzinach, jak trzeba bądź surowy i stanowczy i kłam, że release jest miesiąc wcześniej niż w rzeczywistości". A tu się okazało, że koleś ciekawie nawija o neurologi co jest o tyle interesujące, że trudno filozofować o neurologii bo to jest tak jak z kompilacją kodu - jak działa to działa a jak nie to nie.

*   *   *

wtorek, 26 sierpnia 2014

Expression problem nie tylko dla orzełów

Zagadnienie, które będzie poniżej plastycznie opisane kwiecistym językiem ma się tak do codziennego programowania jak zawody formuły 1 do jazdy osobówką po mieście. Niby nie widzimy na ulicach formułek a jednak stanowią one ciekawy poligon dla rozwiązań technicznych, które po pewnym czasie lądują w normalnych samochodach

Z drugiej jednak strony fakt, że nie mam pojęcia o zawodach formuły 1 może sprawić, że powyższe porównanie jest nieprawdziwe i bez sensu. Tak czy inaczej najtrudniejszą część - czyli wstęp - mam już za sobą i można jechać dalej...

Więcej niż "polimorfizm"

Przez lata moje rozumienie słowa "polimorfizm" sprowadzało się do "jest interfejs i jego kilka implementacji" i było swoistym uwiecznieniem dobrego projektu obiektowego. Pierwsza ciekawa nowinka(dla mnie) - można to samo uzyskać bez żadnego interfejsu zwykłymi generykami - a dwa - nie ma się czym podniecać bo jak wszystko w życiu ma to swoje wady i zalety.

Na wzmiankę o wadach takiego rozwiązania natrafimy w miejscu, którego się nie do końca spodziewamy - czyli w rozdziale zdaje się szóstym "Clean Kodu" o Obiektach i Strukturach danych. Właśnie tam opisany jest ciekawy przypadek kiedy to użycie niesławnego instanceof może nam bardziej pomóc niż zaszkodzić. Gdy mamy sytuację gdzie dochodzą dochodzą nam nowe typy to rozwiązanie z interfejsem ładnie w to wchodzi ale już przy nowych operacjach, na typach jednak wbrew intuicji i powszechnej poprawności projektowej - instance of sprawdza się lepiej.

I to jest moment na wzmiankę o ...

Expression problem

Genezę problemu można sobie znaleźć tutaj -> http://en.wikipedia.org/wiki/Expression_problem lub tutaj : Expression Problem. Co więcej możemy legalnie ściągnąć profesjonalną pracę naukową samego Oderskiego -> PDF Oderskiego

Niestety albo stety prace naukowe mają do siebie, że brak tam rysunków i animacji rodem z "head first" albo też multimedialnych książek kucharskich. Aby więc było łatwiej zamiast używać oryginalnego problemu z rozwijaniem języka wyrażeń naukowych skupimy się na przykładzie z "Clean Code" - kółka i kwadraty (bo "trójkąty i kwadraty" to już chyba będzie plagiat).

  • M1 - tworzymy interfejs kształt i klasę kółko
  • M2 - dodajemy nowy typ kwadrat - tu nie ma schodów
  • M3 - dodajemy nową operację na kółku - tu są schody
  • M4 - a tutaj tę nową operację dodajemy też do kwadratu

M1

//1
object ExpressionsBlog extends M1{
  //2
 val circle:Shape=new Circle(5)            //> circle  : com.wlodar.expressions.poligon.ExpressionsBlog.Shape = com.wlodar.
                                                  //| expressions.poligon.nablog.M1$Circle@72067564
}

package nablog{
 //3
 trait M1{
  
  trait Shape{
   def area:Double
  }
  //4
  class Circle(r:Int) extends Shape{
   def area=Math.PI*r*r
  }
  //5
  def sum(s1:Shape,s2:Shape)=s1.area+s2.area
 }
}

  1. Traity w Scali najłatwiej wytłumaczyć porównując je do interfejsów w Javie ale tutaj widać także ich trochę inną naturę - w tym miejscu trait M1 wzbogaca moduł/obiekt ExpressionsBlog w nowe zachowanie i nowe typy(tutaj Shape i przykładowa implementacja Circle)
  2. Ponieważ kompilator Scali sam zgaduje typ zmiennej, więc aby uogólnić ów typ do Shape musimy jawnie zadeklarować kółko jako właśnie Shape
  3. Mamy dwa traity : M1 i Shape - widzimy (mam nadzieję) także dwoistą naturę tego konceptu - "Interfejs" i "Moduł". Możemy sobie wewnątrz deklarować klasy itd itp
  4. Klasa jak klasa - standardowe rozszerzenie Shape z implementacją metody
  5. A tutaj mamy metodę sum, która będzie służyła do ilustracji kilku ciekawych faktów i eksperymentów okołopolimorfizmowych.

M2

//1
trait M2 extends M1{
  class Square(a:Int) extends Shape{
   def area=a*a
  }
 }

//2
trait M2Rectangle extends M2{
  class Rectangle(a:Int,b:Int) extends Shape {
   def area=a*b
  }
 }
  1. Dodanie nowego typu jest naturalne i nie wymaga żadnego "wzorca" - co podkreślam by czytelnik dopuścił do siebie fakt, iż wzorzec pomimo "efektu aureoli" cudnego rozwiązania może być zwykłym nadmiarowym szumem
  2. No i Traity można rozwijać niezależnie
//1
object ExpressionsBlog extends M2 with M2Rectangle{  
 val circle:Shape=new Circle(5)            //> circle  : com.wlodar.expressions.poligon.ExpressionsBlog.Shape = com.wlodar.
                                                  //| expressions.poligon.nablog.M1$Circle@57a220c2
   //2                      
 val square=new Square(3)                  //> square  : com.wlodar.expressions.poligon.ExpressionsBlog.Square = com.wlodar
                                                  //| .expressions.poligon.nablog.M2$Square@23557525
 //2
 val rectangle=new Rectangle(3,4)          //> rectangle  : com.wlodar.expressions.poligon.ExpressionsBlog.Rectangle = com.
                                                  //| wlodar.expressions.poligon.nablog.M2Rectangle$Rectangle@164af41d
 
}
  1. Przykład dodania dwóch traitów do modułu
  2. Dzięki temu mamy dostęp zarówno do kwadratu jak i prostokąta

M3

//1
trait M3 extends M1{
//2
  trait Shape extends super.Shape{
   def perimeter:Double
  }
//3
  class Circle(r:Int) extends super.Circle(r) with Shape{
   def perimeter=2*Math.PI*r
  }
 }
  1. Przy dodawaniu nowej operacji zaczynają się schody - nie da się wykonać tego tak naturalnie jak w przypadku dodawania nowych typów
  2. Aby zrodzić iluzję dodanej metody przesłaniamy trait Shape z M1
  3. To samo robimy z klasą ale tutaj jeszcze dodatkowo implementujemy przesłonięty Shape
//1
object ExpressionsBlog extends M3 with M2Rectangle{
  
 val circle:Shape=new Circle(5)            //> circle  : com.wlodar.expressions.poligon.ExpressionsBlog.Shape = com.wlodar.
                                                  //| expressions.poligon.nablog.M3$Circle@331f9cee
   //2
   circle.perimeter                               //> res0: Double = 31.41592653589793
   circle.area                                    //> res1: Double = 78.53981633974483
 
}
  1. Cały czas implementujemy dwa niezależne rozszerzenia M1 - za chwilę stanie się z tym coś ciekawego.
  2. Kółko dostało obydwie metody area i perimeter

M4

//1
trait M4 extends M2 with M3{
//2
  class Square(a:Int) extends super.Square(a) with Shape{
   def perimeter=4*a
  }
 }
  1. Scalamy oba kierunki w jeden moduł - tego chyba nie da się zrobić w Javie - nawet ósemce z "defaultowymi metodami" bo jest konflikt nazw
  2. Standardowe Przesłoniećie klasy - klasę bazową bierzemy z jednego modułu a nową operację z drugiego
Niby problem się rozwiązał ale teraz zaczną się dopiero ciekaw rzeczy....

Niebezpieczeństwo

W opracowaniu Oderskiego wykorzystany jest dodatkowo typ abstrakcyjny- na początku zupełnie nie rozumiałem po co skoro wszystko działało i bez tego. No i to był problem bo ... działało za bardzo. Zerknijmy na to co jest w M1

 trait M1{
  //....
  def sum(s1:Shape,s2:Shape)=s1.area+s2.area
 }

I teoretycznie mogę tutaj przekazać coś co implementuje Shape z M1 jak i Shape z M3. Ale co jeśli to jest niedobre, bardzo bardzo złe i nie chcę tego? No to teraz czas włąśnie na...

Typ Abstrakcyjny

Aby pokazać pełną abstrakcję typu abstrakcyjnego nazwiemy go pomidor
trait M1 {
    //...
    //1
    type pomidor <: Shape
    //2
    def sum(s1: pomidor, s2: pomidor) = s1.area + s2.area
  }
  1. Najtrudniejszym elementem w tym miejscu jest chyba ten znaczek : "<:" wzięty prosto z gry PACMAN - a oznacza on zwyczajnie, że pomidor będzie podtypem kształtu.
  2. Nasza metoda nie przyjmuje teraz kształtów ale dwa pomidory. Wydaje się, że za wiele zmian nie zobaczymy w porównaniu do zwykłego dziedziczenia ale teraz tak naprawdę mogę dokładnie sterować czym jest pomidor - i jak będę chciał to nie będzie miał nic wspólnego z Shape z M1
rait M3 extends M1 {
    //1
    trait Shape extends super.Shape {
      def perimeter: Double
    }
    //2
    type pomidor = Shape
    //....
  }
  1. Pamiętamy, że Shape z M1 jest przesłonięty przez nowy Shape
  2. I ustalamy, że pomidor to właśnie nasz nowy Shape czyli to co nie implementuje Shape z M3 nie ma wstępu do metody
Czyli to się nie skompiluje :
object Testy extends M4 with M2Rectangle {
  def test = {
    val circle: Shape = new Circle(5)
    val rec = new Rectangle(3, 4)
//rec i circle to dwa różne szejpy!
    sum(circle, rec)
  }
}

Polimorfizm inaczej

Metoda sum zdefiniowana w M1 dodaje pola dwóch figur. Stwórzmy sobie sztuczny problem i załóżmy, że po dodaniu nowej metody w M3 chcielibyśmy mieć kontrolę nad tym jak to sum jest liczone :

trait M1 {
   //....
    type pomidor <: Shape
//2
    trait Summarizer{
     def add(s1:pomidor,s2:pomidor):Double
    }
    object AreaSummarizer extends Summarizer{
     def add(s1:pomidor,s2:pomidor)=s1.area+s2.area
    }
//1
    def sum(s1: pomidor, s2: pomidor)(s:Summarizer) = s.add(s1,s2)
  }
  1. Definiujemy taką niby strategię - sposób dodawania pomidorów jest zdefiniowany poza metodą sum
  2. Summarizer to zwykły interfejs
I teraz w M3 mogę sobie zdefiniować sumowanie po obwodzie :
 trait M3 extends M1 {
//................................
    type pomidor = Shape   
    object PerimeterSummarizer extends Summarizer{
     def add(s1:pomidor,s2:pomidor)=s1.perimeter+s2.perimeter
    }
  }

sum(circle, sq)(PerimeterSummarizer)
Jest jeden mały problem - API troszeczkę ssie pałkę. Zobaczmy co można z tym zrobić :
//1
def sum(s1: pomidor, s2: pomidor)(implicit s:Summarizer) = s.add(s1,s2)
//2
implicit val summarizer=PerimeterSummarizer
//3
sum(circle, sq)
  1. Dodajemy słówko "implicit" co oznacza, że summarizer będzie wyszukiwany przez kompilator i jak go znajdzie to sam go wstawi jako parametr (a jak nie to będzie błąd). Uwagę czytelnika niech zwróci także druga para nawiasów.
  2. Gdzies tam w module deklarujemy domyślnego summarizera
  3. API jest znowu czyste - jak chcemy to cały czas możemy przekazać kolejny argument, który będzie różny od implicit
I uwaga można z tym iść jeszcze dalej :
trait M1 {
    //....
    type pomidor <: Shape
    
//1
    trait Summarizer[A]{
     def add(s1:A,s2:A):Double
    }
//2
    object AreaSummarizer extends Summarizer[pomidor]{
     def add(s1:pomidor,s2:pomidor)=s1.area+s2.area
    }
//3
    def sum[A](s1: A, s2: A)(implicit s:Summarizer[A]) = s.add(s1,s2)
  }
  1. Summarizer ma generyka A
  2. AreaSummarizer określa generyka A jako pomidor
  3. I sum ma tez generyka A
Ej i teraz obadajcie to :
trait M3 extends M1 {
//....
    type pomidor = Shape
//1
    implicit object PerimeterSummarizer extends Summarizer[pomidor]{
     def add(s1:pomidor,s2:pomidor)={println("pomidor");s1.perimeter+s2.perimeter}
    }
  }


 trait M2Rectangle extends M2 {
//2
  implicit val summarizer=new Summarizer[Rectangle]{
     def add(s1:Rectangle,s2:Rectangle)={println("rectangle");s1.area+s2.area}
    }
  
    class Rectangle(a: Int, b: Int) extends Shape {
      def area = a * b
    }
  }

def test = {
    val circle = new Circle(5)
    val rec = new Rectangle(3, 4)
    val sq = new Square(6)
    
    sum(circle, sq) // pomidor
    sum(rec, rec) // rectangle
  }

Normalnie kompilator sam wstrzykuje to co trzeba na podstawie typu! W "1" mamy obsługę Shape w "2" prostokąta ze starym shape z M1 i wio. Ale jest jeszcze bardziej pojechany mechanizm...

Typy Fantomowe

Najpierw ogólna koncepcja :
//1
package phantoms1{
 sealed trait NotValidated
 sealed trait Validated
 sealed trait Normal
 sealed trait Admin
 
 case class User[A,B]
}
//2
def validate[B](u:User[NotValidated,B])=user.copy[Validated,B]
def secret[B](u:User[Validated,B])=println("sekrety")

//3
val user=new User[NotValidated,Normal]
val validatedUser=validate(user)

//4
secret(validatedUser)                           //> sekrety
//5
secret(user)

  1. Deklarujemy serię traitów, która tak naprawdę nigdzie nie będzie deklarowana - dlatego własnie fantomy
  2. Tutaj jest istota przykładu - mamy taką niby walidację w czasie kompilacji poprzez sprawdzenie czy user ma odpowiednie generyki a może je mieć tylko wtedy jeśli przeszedł metodę "validate"
  3. Na początku user miał generyka NotValidated
  4. Jeśli przeszedł walidację i generyk zmienił się na Validated to wyswietli sekret
  5. A jak nie to wywali sie kompilacja
Jak to wykorzystać w głównym przykładzie?
trait Summarizer[A,B]{
     def add(s1:A,s2:A):Double
    }
    trait ZwyklySummarizer
    implicit object AreaSummarizer extends Summarizer[pomidor,ZwyklySummarizer]{
     def add(s1:pomidor,s2:pomidor)={println("shape z M1");s1.area+s2.area}
    }
    def sum[A](s1: A, s2: A)(implicit s:Summarizer[A,ZwyklySummarizer]) = s.add(s1,s2)

//////////
trait NiezwyklySummarizer
    implicit object PerimeterSummarizer extends Summarizer[pomidor,NiezwyklySummarizer]{
     def add(s1:pomidor,s2:pomidor)={println("pomidor z M3");s1.perimeter+s2.perimeter}
    }
////
implicit val summarizer=new Summarizer[Rectangle,ZwyklySummarizer]{
     def add(s1:Rectangle,s2:Rectangle)={println("rectangle");s1.area+s2.area}
    }

//
   sum(circle, sq) //shape z M1
    sum(rec, rec) //rectangle
No i "NiezwykłySummarizer" z M3 został olany i w jego miejsce wszedł "Zwykly" z M1. Przykład trochę na siłę ale stworzony w celach czysto edukacyjnych.

Podsumowanie

  • W programowaniu czeka na nas wiele ciekawych rzeczy
  • Warto czasem czytać referaty i opracowania naukowe i zwalczyć naturalną niechęć do tego typu prac wykształcony w nas przez system szkolnictwa polskiego.
  • a tu jest cały kod
*   *   *

niedziela, 10 sierpnia 2014

Funkcje częściowe i uczulenie na instanceof

To będzie wpis kontynuujący zachętę do stosowania do szukania nowych rozwiązań "Game of life" na coderetreat. Będzie także o wyciąganiu pochopnych wniosków.

Kompozycja funkcji częściowych

By nie tracić czasu i się nie potwrzać --->Tutaj<--- jest link do opisu co to jest coderetreat.

A co do dzisiejszego mięska. Funkcje częściowe w Scali i (chyba nie tylko) zwracają jakiś sensowny rezultat tylko dla określonych argumentów ale zamiast samemu pisać ify sprawdzające parametry wywołania - jest to naturą samej funkcji częściowej iż taki "if" istnieje niejako w niej samej.

I tak poniżej mamy dwie funkcje częściowe, jedna dla żywej komórki a druga zaś dla martwej. Magiczny patent dzieje się w linii 69 gdzie obydwie funkcje łączymy w jedną w taki sposób, że jak jedna nie może obsłużyć argumentu to spróbuje zrobić to druga. Jak ktoś lubi wzorce to to jest taki "chain of responsibility" (w wersji dla dresa : "złoty łańcuch zobowiązań")

//tutaj mamy taki mocno naciągany closure ale na potrzeby przykładu styka
    val liveNeighbours=4   
 
 def liveCellEvolution:PartialFunction[Cell,Cell]={
  case cell:LiveCell if (liveNeighbours==2 || liveNeighbours==3) =>LiveCell()
  case cell:LiveCell => DeadCell()
 }                                         

 def deadCellEvolution:PartialFunction[Cell,Cell]={
  case cell:DeadCell if (liveNeighbours==3) => LiveCell()
  case cell:DeadCell => DeadCell()
 }    

//linia numer 69
    val cellEvolution=liveCellEvolution orElse deadCellEvolution

Jeśli będziemy chcieli coś zmienić w logice ewolucji komórek wystarczy zrobić ooo tak :


val newCellEvolution=newLiveCellEvolution orElse cellEvolution
//
Czy coś tam w ten deseń.

I gdzieś tam hen hen jest ogólna niezmienna logika ewolucji :

def evolve(evolution:Cell=>Cell){
    cells.map(evolution)
 } 
Znowu jak ktoś bardzo chce to może powiedzieć, że jest zastosowany wzorzec strategia (I mamy kolejny wzorzec przy wykorzystaniu tylko jednego mechanizmu funkcji)

Płachta na byka

Działa powyższy mechanizm nawet fajnie i ciekawie ale jest i druga strona medalu. Powyżej zastosowałem taki skrócony zapis funkcji częściowej - pełna wersja wyglądałaby tak :


val liveCellEvolution=new PartialFunction[Cell,Cell]{
  def apply(c:Cell)=if(liveNeighbours==2 || liveNeighbours==3) LiveCell() else DeadCell()
  def isDefinedAt(c:Cell)=c.isInstanceOf[LiveCell]
 }  

Ahaaaaa i oto mamy profanację jego dostojności OOP poprzez bluźnierstwo w postaci InstanceOf. w sensie, że moim zdaniem akurat tutaj nie ma w tym nic złego ale jak rok temu jeszcze w Javie pokazałem podobny przykład to poleciały pomidory i na widły mnie chcieli nabić·

Skąd się bierze taka wrogość do "instanceOf". Wydaje mi się, że łatwo to wytłumaczyć bo sam to miałem.

Rekonstrukcja zdarzeń

Wszystko zaczyna się na uczelni gdzie skutkiem kombinacji i przypadków można przejść do 3 czy 4 roku (a nawet skończyć studia) pisząc cały czas programy w main. Istnienie takiej konstrukcji jak Interfejs pozostaje zagadką gdyż studentowi bez odpowiedniego doświadczenia trudno zrozumieć po co jest "klasa", która nie ma metod i w zasadzie nic nie robi?

Potem może być etap 2 kiedy to odkrywa się "polimorfizm w Javie" i odchodzi od metod z setkami ifów w kierunku ukrywania implementacji za interfejsem - o na przykład tak(po staremu) :

for (Invoice invoice : invoices) {
   invoice.process(cosTam)
}
I pewnego dnia wzrok nasz może zaatakować taka konstrukcja :
if(invoice instanceof TakiInvoice){
   //...
  }else if(invoice instanceof SrakiInvoice){
  //.... 
  }
No i wiadomo, ze w pierwszym przypadku jest dobrze bo nie trzeba zmieniać kodu jak dodamy nowe typy a w drugim już jest w tym temacie gorzej bo kod modyfikować by trzeba.

Tutaj można wyciągnąć pochopny wniosek, że istnienie "instanceof" oznacza brak polimorfizmu = a to tak nie do końca bo być może...

Szukamy nie tu gdzie trzeba

Zazwyczaj na coderetreat w rozwiązaniu z nutką obiektowości dochodzi się do rozwiązania gdzie mamy dwie komórki : LiveCell i DeadCell rozszerzajace jakiś wspólny interfejs czy klasę abstrakcyjną. Czyli coś takiego :

W przedstawionym na początku przykładzie nie ma czegoś takiego ale ale.... polimorfizm jest w innym miejscu. Przejawia się on niejako pod postacią operacji na typach a nie na typach samych w sobie.

No i ponownie nie ma się co spinać tylko zaobserwować różnice i wybrać co lepsze w danej sytuacji. A jak ktoś jest uczulony na samo słówko "instanceOf" to może sobie wybrać formę zapisu, która to ukrywa i po kłopocie :)

A no i jest jeszcze efekt krugera dunninga - jak już ktoś przeczyta "head first design patterns" i odkryje polimorfizm to może mu się wydawać, że jest na końcu drogi - kiedy tak naprawdę znajduje się na początku...

Mobilization call for papers

Mobilization 2014 call for papers --> Jest tuaj