niedziela, 14 grudnia 2014

Personifikacja risorsów

Ten proces możesz zaobserwować samemu lub samej (w zależności do tego czy jesteś sam czy sama). Następnym razem gdy będzie jakoś segregować czy kategoryzować informacje upraszczając niejako ich formę - sprawdź czy poczujesz się wtedy lepiej czy gorzej? To jest niezwykle ciekawy temat na inny artykuł ale po pewnym czasie chyba każdy może wyćwiczyć u siebie zmysł samoobserwacji aby zauważać kiedy "układ nagrody" strzela z dopaminy. Następnie możemy możemy przejść do bardziej abstrakcyjnej analizy dlaczego np. dzieje się to na widok ciastka,wódy czy czegoś innego czego nie powinno się nadużywać --> http://en.wikipedia.org/wiki/Introspection

Za tą strzałką ---> Jakie diagramy lubimy najbardziej znajduje się link do artykułu z początku bieżącego roku, w którym podałem kilka badań pokazujących jak percepcja wizualna złożoności diagramu wpływa na odczucie czy to co znajduje się na rysunku ma merytorycznie sens czy nie. Jest to dosyć ważne - ba nawet rzekłbym niesamowicie ważne gdyż mam taką sobie intuicję, że ludzie a szczególnie ludzie w IT myślą, że ich proces decyzyjny jest maksymalnie rzeczowy i merytoryczny (a jeszcze sto tysięcy lat temu rzucaliśmy się bananami).

To jest już trzeci akapit a jeszcze nie wiadomo o czym ma być ten artykuł. Już mówię. Generalnie jeśli projekt klas wydaje się lepszy gdy diagram jest relatywnie przejrzysty to co się stanie jeśli zamiast klasy i modułów wstawimy tam ludzi? I uwaga to nie będzie tylko post o narzekaniu ale na końcu pojawi się pewne rozwiązanie, które w moim odczucia przyniosło pewne pozytywne rezultaty.

Co lubi organizacja?

Im większa firma tym silniejsze procesy unifikacji w niej działają. Jeśli 10000 ludzi pracuje w firmie i owa firma ma hierarchię (czyli jakieś 99,999% przypadków) pojawią się procesy upraszczające rzeczywistość do formy łatwej w ogarnięciu dla wyższych poziomów hierarchii (czytałem o próbach stworzenia struktury organizacji coś na wzór fraktali --> http://en.wikipedia.org/wiki/Holacracy ale nie wiem czy tam to będzie wyglądać jakoś inaczej).

Uproszczenie polega na tym, że zamiast 10000 indywidualnych historii życia i unikalnych zdolności mamy kilka ról jak np. Junior Developer, Architekt itd. Pojawiają się często też takie matryce, ze np. znajomość Javy=5 a SQL=4 (Cokolwiek to miałoby znaczyć). I w ten sposób złożoność informacji wraca do stadium wygodnego do operowania w pamięci krótkotrwałej --> http://en.wikipedia.org/wiki/The_Magical_Number_Seven,_Plus_or_Minus_Two

A co lubią ludzie?

To żadna nowość, że sformułowanie "trybik a maszynie" ma raczej skojarzenie pejoratywne (nie wiem czy używam tego słowa w dobrym znaczeniu ale miało nie być bluzg to musi być jakieś zastępstwo).

Wszelkie profile społecznościowe, fejsy,twitery dają coraz więcej opcji aby profil użytkownika był unikalny. I chyba w tej całej hipsterni też chodzi o to aby być maksymalnie różnym od mainstreamu.

Istnieje jeszcze jeden niezwykle ale to niezwykle ważny aspekt całej sprawy (ale to niezwykle ważny). Przy coraz bardziej przyśpieszającym rozwoju technologii dla każdej firmy IT skarbem po prostu skarbem są ludzie, którzy dbają o własny rozwój i dokształcają się we własnym zakresie (no chyba są skarbem). I istnieje naturalny mechanizm motywacyjny, który temu sprzyja --> z tak zwanych popularnych motywatorów wewnętrznych : Autonomia,Mistrzostwo,Poczucie celu -> to będzie Mistrzostwo

Ewolucyjnie to działało tak, że uzyskując unikalną zdolność w ramach grupy osobnik uzyskiwał specjalny status i respekcior w tej grupie. Ale jak można uzyskać specjalną zdolność jak wszyscy w grupie to "Inżynier" o znajomości Java=4? Jak mogę pokazać, że posiadam unikalną wiedzę w zakresie kolekcji, wielowątkowości czy przetwarzania synchronicznego? A może umiem łatwo przekazywać informacje?

I w tym momęcie nadchodzi....

Introducing Corpo RPG

Cała idea polega na tym aby upiec dwie pieczenie na jednym ogniu : dostarczyć odpowiednie uproszczenie informacji do wyższych poziomów hierarchii w firmie a jednocześnie zachować "ludzkość" osób w zespole, informacje o ich unikalnych zdolnościach i osiągnięciach oraz przedstawić jedyną w swoim rodzaju historię ich samorozwoju.

Wymyśliliśmy sobie zestaw umiejętności w których można było się rozwijać i do którego cały czas coś dorzucamy. Co bardzo ważne - każda umiejętność ma motywacje finansową bo generalnie ludzie na ważnych stanowiskach w firmie lubią rozmawiać o pieniądzach.

Bazki

Paypale

Front cześć 1
Front cześć 2

Funcjonalność

Mobilki Mobilki

Deploymenty

Performens

Nawijka

Rozwój innych:

Współpraca poza zespołem :

Automatyzacja testów

Testy widoku

Analytiksy

Kilka klas ma swój specjalny opis np :

Do tego za jednorazowe osiągnięcia dostajemy perki

CAB Driver
I specjalnie dla studentów

Takze ten tego - ludzie w zespole mówili, że im się podoba i menadżerowie wyższych szczebli też mówili, że im się podoba - przynajmniej tak mówili :)

czwartek, 4 grudnia 2014

Ciekawsze modele wielowątkowe (a Akka) + sprawy społecznościowe

Kącik społecznościowy

Jutro zapraszamy na prezentację Marcin Grzejszcza­ka Mikroserwisy

JUG Łódź Misja

Cezary Draus - zarząd JUG niezależna improwizacja o misji JUG Łódź.

moim zdaniem warto by propagować przede wszystkim samorozwój
jako sposób na życie
no ludzie tego nie kumaja chyba
pewnie mysla ze sa IT arystokracja
bo tak im się wmawia
a do tego idzie niz demograficzny
wiec raczej ludzi zdolnych wiecej nie bedzie
a samo sie nie nakurwi
Golf R sam się nie kupi
musisz kurwa sie dorobic
albo krasc
albo sie kurwic
albo sie nauczyc
wybór należy do Ciebie

a samo się nie nakurwi - to od tej chwili nieoficjalna hasło szczęścia poprzez samorozwój.

Właściwy artykuł

Czy jest sens nauki czegoś co wydaje się w danej chwili niepotrzebne(to pytanie retoryczne także nie należy się nad tym zastanawiać tylko czytać dalej)? Przez wiele lat web development wyglądał dla mnie bardzo jednowątkowo. Zazwyczaj jakiś serwer ogarniał requesty przy pomocy swojej puli wątków a ja jako programista musiałem tylko uważać na globalny stan podczas gdy cała reszta szła od góry do dołu po linii prostej (zwaliduj dane, wywołaj jakieś funkcje i zapisz do bazy danych)

Teraz procki mają coraz więcej rdzeni a sam PlayFramework na dzień dobry 4 różne pule wątków i trzeba ten temat zacząć ogarniać. Istnieje kilka ciekawych pozycji o wątkach w Javie i np. taka "Java Concurrency in Practice" nie tyle opisuje jak dobrze zabrać się do tematu ile przedstawia na jak wiele złych sposobów można to zrobić.

Dlatego też warto się zainteresować pewnymi rozwiązaniami wysokopoziomowymi, które pojawiają się tu i ówdzie a w których nie trzeba aż tak bardzo wchodzić w interakcje pomiędzy poszczególnymi wątkami.

A jeśli to nie wystarczy to...

... Historia Hrabiego Monte Christo opowiada historię kolesia, który w swojej naiwności daje się złapać w intrygę swoich fałszywych przyjaciół i ląduje w więzieniu na wyspie gdzie przypadkiem poznaje innego kolesia, który ma wiedzę o wszystkim i tę wiedzę(+ gdzie jest skarb) przekazuje głównemu bohaterowi. Cały proces trwa wiele lat ale nasz ziom w końcu ogarnia swoją ignorancję i zdobywa nowe, pełniejsze spojrzenie na świat.

W przypadku tej historii potrzebny był szok by wyzwolić naukę - a mamy przecież o wiele przyjemniejsze opcje - jak np. czytanie ciekawego internetu zamiast oglądania telewizji i takich tam.

Jak związek ma ta historia z nowoczesnymi modelami programowania wielowątkowego? Żaden ale przynajmniej stanowi ciekawy przerywnik, daje uzasadnienie dla wklejenia fajnego obrazka i być może zabija wrażenie, że poniższa treść jest zwyczajnie spisana z internetu. Ale wracając do tematu...

Klasyka

Jak zwykle za pomoc edukacyjną posłuży naciągany problem, który można łatwo rozwiązać za pomocą AtomicInteger - ale dla potrzeb ćwiczenia załóżmy, że nie można

  • Globalny stan - licznik
  • Kilka wątków, które wchodzą sobie w drogę
object LockingExerciseSimple {

  private var counter = 0

  def main(args: Array[String]) {
    val threads = (1 to 100).map(_ => createThread())
    threads.foreach(_.start())
    threads.foreach(_.join())
    println(counter)
  }

  def createThread() = new Thread {
    override def run() = {
      counter = counter + 1
    }
  }
}

Uruchamiając ten kod nigdy nie powinniśmy otrzymać wyniku 100 gdyż ile tam wątków najedzie na siebie i nadpisze licznik.

By rozwiąπać problem klasycznie tworzymy "loka" wokół inkrementacji licznika i zaczynamy się zastanawiać czy o czymś przypadkiem nie zapomnieliśmy co może zakończyć się zakleszczeniem.

  • Globalny stan - licznik
  • Kilka wątków, które wchodzą sobie w drogę
  • Blokowanie wątków na locku
object LockingSolutionSimple {
 private var counter = 0
 private val lock=new ReentrantLock()

  def main(args: Array[String]) {
    val threads = (1 to 100).map(_ => createThread())
    threads.foreach(_.start())
    threads.foreach(_.join())
    println(counter)
  }

  // TO JEST WAZNY FRAGMENT
  def createThread() = new Thread {
    override def run() = {
      lock.lock()
      try{
       counter = counter + 1
      }finally{
        lock.unlock()
      }
    }
  }
}

Agent

  • Stan nie jest dostępny globalnie
  • Dostęp synchroniczny
  • Modyfikacje asynchroniczne

Generalnie agent to specyficzny rodzaj aktora - czyli warto w pierwszej kolejności wspomnieć o aktorach. Ale aktorzy są na topie i każdy o nich wspomina i takie powielanie opisów byłoby nudne.

Gdzieś na githubie widziałem wątek, w którym jacyś ludzie implementujący bugfixy w akka dyskutowali czy jak w tym a tym miejscu będzie volatile to dobry asembler javy powstanie czy coś w ten deseń. To co chcę tutaj powiedzieć,to to że jakieś mądre głowy zadbały już o to, że logika w aktorze wykona się jednowątkowo i nie będzie deadlocków.

Agent to właśnie taki specjalny aktor, który z założenia kontroluje dostęp do konkretnej zmiennej. Przystosowanie polega na tym, że ów stan zmiennej można pobrać synchronicznie w dowolnym momencie.

object AgentsSolution {
  import akka.agent.Agent
  import scala.concurrent.ExecutionContext.Implicits.global

  private var counter = Agent(0)

  def main(args: Array[String]) {
    //każdy wątek zwiększa stan licznika o jeden
    val threads = (1 to 100).map(_ => createThread())
    threads.foreach(_.start())
    threads.foreach(_.join())
    
    /** 1 updejty są wysyłane do agenta asynchronicznie dlatego na początku get wypisze zero**/
    println(counter.get)
    Thread.sleep(1000)
    // ale już po sekundzie powinna być setka
    println(counter.get)
    
    /**2 
    // w dowolnej chwili można pobrać z agenta future, który zakończy się gdy wszystkie funkcje z danej chwili zostaną wykonane
    val future=counter.future
    future.onComplete {
      case result => println(result)
    }
    Thread.sleep(1000)**/
    

  }
  // TO JEST WAZNY FRAGMENT
  def createThread() = new Thread {
    override def run() = {
      // a tak się wysyła do agenta funkcje, które on wykona jednowątkowo
      counter.send(c => c + 1)
    }
  }
}

STM

To w zasadzie zwykłe transakcje w pamięci

  • Globalny stan opakowany w Ref
  • Zmiany odbywają się w transakcji
  • w przypadku konfliktu mamy rollback
object STMSimpleSolution {
  //zwykle transakcje ale tylko dla danych, które są w Ref
  private var counter = Ref(0)

  def main(args: Array[String]) {
    val threads = (1 to 100).map(_ => createThread())
    threads.foreach(_.start())
    threads.foreach(_.join())
    
    atomic{implicit txn=>
     println(counter.get)
    }
  }

  // TO JEST WAZNY FRAGMENT
  def createThread() = new Thread {
    override def run() = {
      // jeśli dwa wątki wjadą na raz z tą samą wartością to zmiany będą rollbackowane
      // i jedna z transakcji powtórzona
      atomic{implicit txn=>
          val currentValue=counter.get
       counter.set(currentValue+1)
      }
    }
  }
}

Dataflow

  • Nie ma faktów są jedynie obietnice
  • Do zmiennych dane można przypisać tylko raz
  • Każdy flow to inny wątek ale wszystko magicznie się układa

To jest fajne - przeterminowane -ale fajne bo zagina czasoprzestrzeń. Bo zobaczmy chociaż taką sytuację :

    // CAŁY FRAGMENT W ZASADZIE JEST WAŻNY
    val counter = Promise[Int]()
    val v1, v2 = Promise[Int]()

    //tutaj jest dodawanie
    flow {
      threadName()
      counter << v1() + v2()
      println("counter = " + counter())
    } 
    // a dopiero tutaj ustalenie co dodawać
    flow {
      threadName()
      v1 << 40 
    }
    flow {
      threadName()
      v2 << 2 
    }
    // i każdy flow odbywa się w innym wątku
    //ForkJoinPool-1-worker-11
    //ForkJoinPool-1-worker-13
    //ForkJoinPool-1-worker-9

To jest fajne ale już deprecated bo w najnowszej wersji Akka zastąpiła je scala async

Async Aysnc

  • Instrukcje do asynchronicznego odpalania obliczeń w osobnym wątku są tworzone na prawdę bardzo prosto
  • Można określać zależność czasową pomiędzy blokami przy pomocy prostego await
    var counter = 0

    val future1 = async {
      threadName()
      Thread.sleep(300)
      40
    }
    val future2 = async {
      threadName()
      Thread.sleep(200)
      2
    }

    val sumFuture = async {
      threadName()
      counter = await(future1) + await(future2)
      counter
    }

    sumFuture.onSuccess {
      case result => println(result)
    }
//ForkJoinPool-1-worker-13
//ForkJoinPool-1-worker-11
//ForkJoinPool-1-worker-9
//42

Na koniec

Z ciekawych modeli wielowątkowych, o których słyszałem są jeszcze "channels", które na pewno występują w języku Go i bibliotece clojure core.async ale nic o tym nie wiem to też nic nie napiszę ale zakończę artykuł w tym miejscu.

niedziela, 16 listopada 2014

Po Code Retreat 2014

Gdy piszę te słowy przyszły już pierwsze opinie na googlowym formularzu. Ludziom się generalnie podobało a po obecności słów "ch*j" i "du*a" wnioskuję, że studentom także.

Za rok trzeba będzie mocniej się postarać by ludzie mieli więcej szans popełnić błędy w kodzie i się na nich uczyć zamiast rozkminiać samą grę nad kartką. Najlepiej od razu podpowiedzieć kilka niedoskonałych rozwiązań na standardowe pytania - jak reprezentować planszę i od czego zacząć pisać testy.

Przez ostatnie dwa miesiące słyszałem wiele głosów w stylu "ej no znowu ta gra życie, może coś nowego..." a koniec końców chyba nie licząc jednej lub dwóch osób wszyscy uczestniczyli po raz pierwszy i doskonale pojechaliśmy "klasyką".

  1. Sesja wstępna-dowolna
  2. TDD i tylko jeden poziom zagłębienia kodu w metodach
  3. Jeden poziom zagłębienia, metody maksymalnie 5 linii długości i brak else
  4. obiad(oj jedzenia sporo było)
  5. Sesja "cicha" oraz do wyboru albo object-calisthenics lub not only OOP - wszyscy wybrali to pierwsze ;)
  6. Zostawiamy kod z poprzedniej sesji i mamy dodatkowe wymagania

Dominowała Java ale pojawił się też .Net, Ruby i Scala. Im więcej języków tym ciekawiej.

Dziękuję wszystkim za przybycie i organizatorom za zadbanie o wszystkie detale spotkania.

Ściana w kodzie

podobny temat poruszyłem już tutaj : o instanceof

Ciekawy problem powstaje gdy rozwiazujemy zadanie tworząc abstrakcję "Cell" i klasy pochodne "LiveCell" oraz "DeadCell". Jednocześnie gdzieś w kodzie musimy zliczać ilość żywych komórek - jak to zrobić?. Cześć osób dodaje do Cell metodę "isAlive"

Główny problem z tym rozwiązaniem polega na tym, że jeśli wyobrazimy sobie diagram klas to generalnie ten kwadrat co jest na górze nie powinien nic wiedzieć o tych co są na dole (chyba, ze ktoś rysuje do góry nogami) bo jeśli wie to jest ryzyko, że jak się zmieni coś na dole to trzeba będzie zmieniać przez tę zależność dosłownie wszystko. A jeśli w kwadracie na górze mamy metodę "isAlive" to on już wie, że gdzieś tam dziedziczy z niego żywa komórka, a teoretycznie cały bajer polega na tym aby bezboleśnie dodawać nowe podtypy bez mieszania w tym co jest zrobione do tej pory. Jeśli nie tak to jak można inaczej?

Prześledźmy taki tok myślowy :

  • Jeśli ktoś reprezentuje komórki przy pomocy boolean na tablicy to wykorzysta wartość true/false do stwierdzenia czy komórka jest żywa
  • Podobnie jeśli reprezentujemy je przy pomocy 0 i 1 - tutaj też nie będzie problemu.
  • enum {DEAD,ALIVE} - również łatwo rozpoznać typ komórki
  • LiveCell, DeadCell - tutaj można postąpić analogicznie. Generalnie dziedziczenie pozwala nam przenieść część logiki z kodu do kompilatora, który to wybierze odpowiednia implementację przez co sam kod jest bardziej elastyczny. Ale to nie znaczy, że wszystko ma się sprowadzać do tylko i wyłącznie tego mechanizmu. Tutaj łatwo można sprawdzić typ komórki w jakimś niezależnym i łatwym do testowania komponencie przy pomocy budzącego emocje "instanceof". Jeśli używamy tego mechanizmu do sprawdzenia ile elementów danego typu jest kolekcji wtedy raczej jest to ok, błąd wystąpi wtedy gdy używamy go by na podstawie typu wykonać jakieś operacji dla żywej komórki(i co gorsze rzutowanie) - wtedy w zasadzie wracamy do bazowego "if (komorka zywa) evoluujTak else evoluujInaczej"

Email Calisthenics

W trakcie ostatecznej retrospektywy wpadliśmy również na pomysł nowego ćwiczenia - bardziej w kierunku tych ludzie co więcej "komunikują niż programują".

W trakcie "Global Day of Email Retreat" uczestnicy grają w "Grę w życie" i mają ją rozwiązać odpowiednim łańcuchem maili. Ograniczenia.

  1. Respect people's time - Max 3 Recipients per email
  2. Focus - Only one discussion thread per email
  3. Save our eyes - whole email is written with the same font
  4. Don't overreact - No Top Management in CC
  5. Merit Arguments - No personal attacks in an email
  6. Avoid templates - participants can not use words "ASAP,Unacceptable,Concerned,Urgent or Opportunity"
Czy coś w ten deseń...

wtorek, 11 listopada 2014

Nauka Scali i Javy 8

Zanim przejdziemy do pierwszego spotkania-warsztatów przeznaczonych nauce Scali tylko i wyłącznie - wpierw kącik społecznościowy

Kącik społecznościowy

  • W najbliższa sobotę CodeRetreat - zapisy pod linkiem po prawej ---> link po prawej(zapisy) . Niby tam lista jest zamknięta ale dodajcie się na "waitlistę" bo kilka osób jeszcze pewnie zrezygnuje. W tym roku zapraszaliśmy speców z zagranicy do poprowadzenia wydarzenia i chociaż ich nie będzie to przysłali wskazówki jak tam to wydarzenie lepiej poprowadzić
    • Start by asking people what they would like to learn
    • Write down what they say on a flipchart or post-its
    • Group them into categories, and write down the number of "votes" for each category
    • Usually, you get things such as: pair programming, something new, TDD, Refactoring
    • If there are first timers, start with the normal free session that helps familiarize with the problem. Otherwise you can skip it
    • Then pick the categories in the order of votes and select the constraints that answer to those categories. You might need to improvise or ask for help - Adi and I will be available to help
  • A 20 listopada na DMCS odbędzie się wykład Michał Balińskiego o Amazon AWS. Michał jest na tę chwilę chyba najlepszym specjalistą w tych tematach w Łodzi - także na tę chwilę zapisało się już 40 osób i wy zapiszcie się także w linku obok Link obok do zapisów. Może pobijemy rekord publiczności. Obecny chyba nalezy do Grześka Borkowskiego do którego swego czasu przyszło około 70 osób na prezentacje o Javascript
  • .

Pierwsze spotkanie Nauki Scali

Scali warto się uczyć i jestem w pełni świadom tego, że są ludzie którzy pałają do tego języka nienawiścią (sam kiedyś tam byłem) ale jak ktoś nie chce nie musi używać natomiast możliwości edukacyjne są moim zdaniem ogromne :

  • REPL i worksheet są cudowne jeśli chodzi o prezentację uczestnikom tego co dzieje w kodzie. Nie trzeba printlainami iterować po elementach listy bo jest ona niejako "prześwietlana" Jeśli jest coś takiego w Javie to ja nie znaju.
  • Sam zacząłem interesować się scalą i programowaniem pod kątem nauki programowania funkcyjnego zanim Java 8 była gotowa i jeśli chodzi o ten temat to jest on dużo przejrzystszy w scali : funkcja to funkcja bez względu na to ile ma parametrów - gdzie w javie to programowanie funkcyjne jest zrobione najbardziej obiektowo chyba jak można było : Supliery,Consumery, funkcje, bifunkcje (a w dodatku "Updadek Bastionu" będą TriFunkcje i nowe zbroje).
  • W tym języku są koncepcje jak choćby pattern matching, które jak się okazuje (przynajmniej dla mnie było to odkrycie) są także nie obce wielu innym językom. Zamiast czekać nim pojawią się w Javie można z nimi zapoznać się już teraz.
  • I ostatni - moim zdaniem najważniejszy strategicznie punkt - generując anomalię edukacyjną polegającą na wzajemnej nauce relatywnie zaawansowanej technologi bez - (uwaga to bardzo ważne) - bez jasnego zysku komercyjnego - można przerwać zjawisko takiej "łódzkiej samo nakręcającej się spirali gówna" (rysunek dzięki uprzejmości mojej poniżej)

Jeśli ludzie nie byli tylko zwyczajowo mili to ogólnie się podobało. Generalnie idea jest taka aby pojawiła się jakaś grupa ludzi chcąca prowadzić warsztaty i to całe przedsięwzięcie się skalowało - są już chętni także następnym razem poprowadzi ktoś inny itd.

Co należy umieć aby poprowadzić warsztat

Tutaj dam linka sam do siebie - Pewność siebie poprzez niewiedzę. Generalnie najlepszą odpowiedzią co trzeba zrobić aby poprowadzić warsztat ze scali - należy nie wiedzieć, że nie ma się odpowiedniej wiedzy aby go poprowadzić. A później trzeba się tylko zgłosić, opracować materiał i przećwiczyć warsztat aby był ciekawy dla uczestników - i to tylko tyle.

Jak widać sam wykorzystuje tę technikę generując w sobie urojenia, że moje działania jakoś wpłyną na to co się dzieje w tym mieście. Ale dopóki ja mam z tego zabawę a ludziom się podoba i się czegoś uczą to będziemy to robić.

Co więcej

TomTom zaczął się angażować w edukację studentów w Łodzi. Na pierwszych zajęciach mieli okazję poznać trochę Scali i Playframework.

A TERAZ KOD.

Pierwsze porównanie

Na samym początku szybkie porównywanie scali i javy8 pod kątek filtrowania elementów listy przy pomocy lambdy (nie wiem czy to fachowo powinno nazywać się "przetwarzanie batchowe","przetwarzanie strumieniowe"czy też "przetwarzanie deklaratywne"?)

val lista=List(1,2,3,4,5) 
lista.filter(element=>element>2)
zadanie : stworzyć listę elementów 'a','b','c','d','e' i odfiltrować tak by zostało 'd','e'
val lista2=List('a','b','c','d','e')
lista2.filter(e=>e>'c')

Java
List lista = asList(1,2,3,4,5);
// a to nie wystarczy
Stream filter = lista.stream().filter(element->element > 2);
//trzeba tak
List result = lista.stream().filter(element->element > 2).collect(Collectors.toList());

Tworzenie listy

Aby słuchacze się nie nudzili - kilka ciekawych sposobów na inicjację(tudzież tworzenie) listy

List.range(0, 10, 2)
List.fill(7)(42)
List.tabulate(10)(elem=>s"element ${elem}")
"tekst".toList
zadanie : stworzyć listę z kolejnymi kwadratami liczb od 0 do N=20 i zostawić tylko podzielne przez 3
val lista=List.tabulate(20)(e=>e*e)
lista.filter(_ % 3==0)

sortowanie i mapowanie

lista.filter(element=>element>50).sortWith((elem1,elem2)=>elem1>elem2).map(elem=>elem+1)
zadanie : stworzyć listę od 1 do 100, zostawić elementy większe od 50 i posortować tak by parzyste liczby były na początku listy
List.range(1,100).filter(e=>e>50).sortWith((e1,e2)=>e1%2==0)

Java
List result = lista.stream()
    .filter(e->e > 2)
    .sorted(Comparator.reverseOrder())
    .map(e->e+1)
    .collect(Collectors.toList());
  
  result.forEach(System.out::println);
Przy pierwszym zetknięciu się z uproszczeniem zapisu przy pomocy podkreślenia czytelnik może przeżyć szok lecz zauważenie zysków tego zapisu to tylko kwestia czasu i praktyki.
 lista.filter(e=>e>2).sortWith((e1,e2)=>e1>e2).map(e=>e+1)
//lub lepiej
        lista.filter(_>2).sortWith(_>_).map(_+1)

Podzbiory

 val lista=List(1,1,2,1,3,1,4,5) 

lista.takeWhile(_==1)                     //> res0: List[Int] = List(1, 1)
lista.dropWhile(_==1)                     //> res1: List[Int] = List(2, 1, 3, 1, 4, 5)
Stream stream = Stream.of(1,1,2,1,3,1,4,5);
  
  List result = stream
    .limit(5)
    .skip(2)
    .sorted(Comparator.reverseOrder())
    .map(e->e+1)
    .collect(Collectors.toList());
  
  result.forEach(System.out::println);
zadanie : stworzyć listę od 0 do 100 ,posortować po drugiej cyfrze (czyli 19 jest większe od 88) i zostawić początek listy mniejszy od 90.
List.range(0, 100).sortWith((e1,e2)=>(e1%10) > (e2%10)).takeWhile(_<90)

Kilka kolekcji i Flatten

Przy tym cwiczeniu dla zwiększenia satysfakcji uczestników można (a nawet należy) wspomnieć różnicę pomiędzy listą i wektorem.

 val lista=List("elektromagnetyczny","pierun","trzy")
     //Vektor                                             
 val samogloski=Vector('a','e','u','y')    //> samogloski  : scala.collection.immutable.Vector[Char] = Vector(a, e, u, y)
 //Array
 def zostawSamogloski(s:String)=s.toCharArray().filter(samogloski contains(_))
                                                  //> zostawSamogloski: (s: String)Array[Char]

 lista.map(zostawSamogloski)               //> res0: List[Array[Char]] = List(Array(e, e, a, e, y, y), Array(e, u), Array(y
                                                  //| ))
 lista.map(zostawSamogloski).flatten       //> res1: List[Char] = List(e, e, a, e, y, y, e, u, y)
 
 //lub
 lista.flatMap(zostawSamogloski)
zadanie : zdublować przy pomocy flatMap wszystkie elementy listy List(1,2,3) -> List(1,1,2,2,3,3)
List(1,2,3).flatMap(e=>List(e,e))

redukcja

Generalnie redukcję na siłce robi się na wiosnę aby na lato by kaloryfer - tutaj jednak chodzi o redukcję kolekcji do jednej wartości. W poniższym przykładzie będzie również zaprezentowany mały przykład pokazujący różnice pomiędzy metodą i funkcją

val lista=List(1,2,3,4,5)

//redukcja
lista.reduce(_*_) 

//ponowne wykorzystanie funkcji
def add(a:Int,b:Int)=a+b 

lista.reduce(add)  
lista.foldLeft(0)(add)

//ale mozna i
val addf=(a:Int,b:Int)=>a+b  

//jaka jest roznica? Metody moga mieć generyki i na tym etapie tyle tłumaczenia musi wystarczyć :)

val reduceText=(a:Int,b:Int)=>a+":"+b
def reduceTextM[A](a:A,b:A)=a+":"+b

reduceText(1,2)                           //> res1: String = 1:2
reduceText('1','2')                       //> res2: String = 49:50
//BŁĄÐ KOMPILACJI //reduceText("1","2")
reduceTextM(1,2)                          //> res3: String = 1:2
reduceTextM('1','2')                      //> res4: String = 1:2
reduceTextM("1","2")                      //> res5: String = 1:2

lista.reduce(reduceTextM)                 //> res6: Any = 1:2:3:4:5
val lista2=List('a','b','c')                    //> lista2  : List[Char] = List(a, b, c)
lista2.foldLeft("")(reduceTextM)          //> res7: String = :a:b:c

zadanie : treść podobna "zdublować przy pomocy flatMap wszystkie elementy listy List(1,2,3) -> List(1,1,2,2,3,3)" ale tym razem w dwóch wersjach
  1. Funkcji, która przyjmuje listę typu Int i wykonuje wspomnianą operację
  2. Metody, która przyjmuje jakąś listę typu A i wykonuje wspomnianą operację
val double=(l:List[Int])=>l.flatMap(e=>List(e,e))
def doubleM[A](l:List[A])=l.flatMap(e=>List(e,e))

A jak to wygląda w Javie ?
Stream stream = Stream.of(1,1,2,1,3,1,4,5);
  Optional result = stream.reduce((a,b)->a+b);
  result.ifPresent(System.out::println);

Grupowanie

Tutaj zaś zastapimy działanie na intach działaniem na bardziej domenowych obiektach. Zobacyzmy tkaże ile więcej kodu trzeba napisac w Javie by osiągnąc podobny rezultat:(

case class User(name:String,age:Int,gender:String)
 val lista=List(User("Stefan",28,"M"),User("Joanna",20,"F"),User("Elżbieta",28,"F"))
                                                  //> lista  : List[akademia.kolekcje.User] = List(User(Stefan,28,M), User(Joanna,
                                                  //| 20,F), User(Elżbieta,28,F))

 lista.groupBy(_.age)                      //> res0: scala.collection.immutable.Map[Int,List[akademia.kolekcje.User]] = Map
                                                  //| (20 -> List(User(Joanna,20,F)), 28 -> List(User(Stefan,28,M), User(Elżbieta
                                                  //| ,28,F)))
 lista.groupBy(_.gender)                   //> res1: scala.collection.immutable.Map[String,List[akademia.kolekcje.User]] = 
                                                  //| Map(M -> List(User(Stefan,28,M)), F -> List(User(Joanna,20,F), User(Elżbiet
                                                  //| a,28,F)))
eksperymenty :
val random=new java.util.Random()
import Math.abs
val users=List.tabulate(100)(n=>User((n % 30).toString,abs(random.nextInt()) % 30,"F"))
users.groupBy(_.age)
users.groupBy(_.name) 

W javie niestety trzeba się trochę więcej rozpisać :
class User{
 private final String name;
 private final int age;
 public User(String name, int age) {
  this.name = name;
  this.age = age;
 }
 public String getName() {
  return name;
 }
 public int getAge() {
  return age;
 }
 @Override
 public String toString() {
  return "User [name=" + name + ", age=" + age + "]";
 }
}
List<User> users = asList(new User("ccc",12),new User("bbb",20),new User("aaa",12));
Map<Integer, List<User>> result = users.stream().collect(Collectors.groupingBy(User::getAge));

result.forEach((k,vl)->System.out.println(k+":"+vl));

Span i partition

Niech kod przemówi :
val numery=List(1,4,5,78,9,1,2,3)         //> numery  : List[Int] = List(1, 4, 5, 78, 9, 1, 2, 3)
 
 
 numery.partition(_%2==0)                  //> res0: (List[Int], List[Int]) = (List(4, 78, 2),List(1, 5, 9, 1, 3))
 numery.partition(_>3)                     //> res1: (List[Int], List[Int]) = (List(4, 5, 78, 9),List(1, 1, 2, 3))
 numery.partition(_<10)                    //> res3: (List[Int], List[Int]) = (List(1, 4, 5, 9, 1, 2, 3),List(78))
 numery.span(_<10)                         //> res2: (List[Int], List[Int]) = (List(1, 4, 5),List(78, 9, 1, 2, 3))
zadanie : stworzyć listę od 0 do 100 i podzielić ją na dwie listy : w jednej mają być potęgi dwójki a w drugiej liście cała reszta.
import math._
val powersOfTwo=List.range(0, 10).map(n=>pow(2, n).toInt).toSet
List.range(0,100).partition(powersOfTwo contains _)

Widok

Tutaj jest różnica z javą.
val numery=List(1,4,5,78,9,1,2,3)         //> numery  : List[Int] = List(1, 4, 5, 78, 9, 1, 2, 3)
 
// zwrocic uwage na SeqViewMMF 
numery.view.map(_+1).map(_+2).filter(_>4) //> res0: scala.collection.SeqView[Int,Seq[_]] = SeqViewMMF(...)

Zipy i Unzipy

val numery=List.range(13, 27)             //> numery  : List[Int] = List(13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,  26)
numery.zipWithIndex                       //> res0: List[(Int, Int)] = List((13,0), (14,1), (15,2), (16,3), (17,4), (18,5),
                                                  //|  (19,6), (20,7), (21,8), (22,9), (23,10), (24,11), (25,12), (26,13))                                                 
 
 for{
  (e,i) <- numery.zipWithIndex
  } yield s""" """
                                                  //> res1: List[String] = List( ,  ,  ,  ,  ,  ,  ,
                                                  //|   ,  ,  
                                                  //| ,  ,  ,  ,  )
zadanie : stworzyć listę od 0 do 10 i usunąć co trzeci element
val lista=List.range(1,10)
lista.zipWithIndex.filter{case (_,i) => (i+1)%3!=0}.map(_._1)

Mutable i Immutable

val numery=List(1,2,3,4)                  //> numery  : List[Int] = List(1, 2, 3, 4)
 
 numery.+:(5)                              //> res0: List[Int] = List(5, 1, 2, 3, 4)
 numery.+:(5)                              //> res1: List[Int] = List(5, 1, 2, 3, 4)

 
  import scala.collection.mutable.ListBuffer
  val numeryBuffer=ListBuffer(1,2,3,4)            //> numeryBuffer  : scala.collection.mutable.ListBuffer[Int] = ListBuffer(1, 2, 
                                                  //| 3, 4)
  
  numeryBuffer += 5                               //> res2: poligon.poligon.numeryBuffer.type = ListBuffer(1, 2, 3, 4, 5)
  numeryBuffer += 5                               //> res3: poligon.poligon.numeryBuffer.type = ListBuffer(1, 2, 3, 4, 5, 5) 

For

val numbers=Vector(1,2,3,4)
val letters=Vector('a','b','c','d')

for{
  n<-numbers
  l<-letters
 } yield (n,l)

val names = Map("firstname" -> "Roman", "lastname" -> "Kowalski")

for ((k, v) <- names) println(s"key : ${k} , name : ${v}")
eksperymenty :
numbers.map(n=>
    letters.map(e=>s"($n,$e)")
   )                                              //> res1: scala.collection.immutable.Vector[scala.collection.immutable.Vector[St
                                                  //| ring]] = Vector(Vector((1,a), (1,b), (1,c), (1,d)), Vector((2,a), (2,b), (2,
                                                  //| c), (2,d)), Vector((3,a), (3,b), (3,c), (3,d)), Vector((4,a), (4,b), (4,c), 
                                                  //| (4,d)))

   numbers.flatMap(n=>
    letters.map(e=>s"($n,$e)")
   )                                              //> res1: scala.collection.immutable.Vector[String] = Vector((1,a), (1,b), (1,c)
                                                  //| , (1,d), (2,a), (2,b), (2,c), (2,d), (3,a), (3,b), (3,c), (3,d), (4,a), (4,b
                                                  //| ), (4,c), (4,d))

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.

*   *   *