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

niedziela, 27 lipca 2014

Rozszerzenia do "Coderetreat" i "Game of Life"

Global Day of Coderetreat stanowi niesamowitą pomoc w sianiu eksplozji wiedzy w kierunku jednostek programistycznych wciąż uwięzionych w rozwiązywaniu problemów programistycznych przy pomocy tandemu for-if (powtórz n razy). Aby było łatwiej zachęcić ludzi do udziału wydarzenie odbywa się w porze roku kiedy najłatwiej odpalić samochód a wszyscy są opaleni i wypoczęci - czyli na przełomie listopada i grudnia.

Aby nie robić kolejnego CRUDa pracujemy z ciekawszym problemem czyli Grą w życie (nie mylić z "grą życie", którą uprawiamy na codzień). I chociaż ludzie czasem narzakają, że ciągle jest to samo ćwiczenie i można by wrzucić coś nowego to jednak co roku scenariusz przebiegu zdarzeń mówi co innego.

Najpierw większość ludzi jedzie proste rozwiązanie z tablicą dwu wymiarową i wali w komórki albo booleany albo inty. Później mamy trudną do przetestowania gigantyczną metodę main lub kilka mniejszych metod z (niestety) wszędzie rozsianymi iteracjami po tablicach. To czego przez ostatnie dwa lata prowadzenia coderetreat nie udało mi się wyjaśnić to to to to, że rozwiązanie samo w sobie nie jest istotne - właśnie dlatego je wywalamy na końcu (oj jak tego ludzie nie lubią!).

Ważna jest obserwacja, ważny jest proces nauki, ważne jest wyciągnięcie wniosku i zrozumienie jak dana decyzja wpływa na całokształt rozwiązania i dalsze decyzje. Np. fajne byłoby olśnienie, że jeśli każde miejsce kodu jest świadome tego, że operujemy na tablicy dwuwymiarowej to jakakolwiek próba zmiany tego konkretnego aspektu będzie boleć.

Poza Object Calisthenics

Object Calisthenics(link) to zbiór ciekawych ograniczeń, których nazwy nich*ja nie mogę zapamiętać. Ograniczeń - bo formuła ćwiczeń jest dosyć ciekawa - zamiast narzucać (no dobra trochę się narzuca), że dane rozwiązanie jest lepsze - staramy się zaprogramować rozwiązanie z ograniczeniem np."metody maks 5 linii" i pomóc uczestnikom ćwiczeń samemu wyciągnąć wnioski.

Do niedawna Object Calisthenics to była taka obiektowa nirwana ale ale jak już i w javie pojawiły się "strzałki" to warto zerknąć na inne nirwany i zapoznać się z mechanizmami, których w javie do tej pory nie było albo jeszcze nie ma.

Wyrzucić długopisy i ołówki

... i przesiąść się do painta (nie nie to tylko żart prowadzącego).

Generalnie wiele zespołów na początku dużo dyskutuje o rozwiązaniach i nic a nic nie koduje :( i chyba dlatego wymyśloną sesję "nic nie gadamy tylko piszemy". A można zacząć inaczej. Zastanówmy się co jest potrzebne na samiutkim początku? Mamy komórki i jest też jakaś plansza. I tyle wystarczy by zacząć

trait GameOfLifeBlueprint{
 type Board
 type Cell
}

Słówko "type" należy czytać "jest sobie jakiś typ" (w sensie abstrakcyjnym a nie w sensie "patrz na niego ale typ"). I tyle - decyzje co to będzie za typ odkładamy na później...

I tak dalej

No dobra komórki są gdzieś położone na planszy. Przydałoby się je jakoś namierzyć

trait GameOfLifeWithCoordinates extends GameOfLifeBlueprint{
 type Coordinates
 def findCell(c:Coordinates)(implicit b:Board):Cell
}

I już mamy planszę, komórki i współrzędne a cały czas nie wiadomo co tam siedzi - i to jest poniekąd dobre bo mamy wciąż swobodę podejmowania decyzji. No i podejmujemy decyzje. Zamiast bawić się z tablicami przestrzeń za symuluje nam mapa. Kto chce 2D ma 2D a kto DDD ten ma 3D.

trait MapBasedGame extends GameOfLifeWithCoordinates{
 type Board=Map[Coordinates,Cell]
 val board:Board=Map[Coordinates,Cell]()
}

trait GameOfLife2D extends MapBasedGame{
 type Coordinates=(Int,Int)
}

trait GameOfLife3D extends MapBasedGame{
 type Coordinates=(Int,Int,Int)
}

To co jest jeszcze nie określone to Cell - no to wio

trait GameOfLifeWithStandardCells extends GameOfLifeWithCoordinates{
 trait GameCell
 case class LiveCell() extends GameCell
 case class DeadCell() extends GameCell
 type Cell=GameCell
 implicit val board:Board

 def lifeNeighbours(c:Coordinates)(implicit b:Board):Int
 
 def evolve(c:Coordinates):Cell=(findCell(c),lifeNeighbours(c)) match {
  case (DeadCell(),3)=>LiveCell()
  case (LiveCell(),2)=>LiveCell()
  case (LiveCell(),3)=>LiveCell()
  case _ => DeadCell()
 }
}

Przydałoby się ogarnąć jakieś zalecenia co i w jakiej kolejności zaleca się deklarować. W każdym razie powstało coś na wzór Metody Szablonowej także jak ktoś chce jakies wzorce projektowe to je ma.

No to gramy...

...ale jeszcze nie teraz bo nie chce psuć zabawy uczestnikom (a tak naprawdę nie chce mi się tego implementować bo jeszcze muszę dzisiaj spodnie sobie wyprasować). W każdym razie będzie to mniej więcej wyglądało tak :

object NormalGame extends GameOfLife2D with GameOfLifeWithStandardCells{
 def lifeNeighbours(c:Coordinates)(implicit b:Board):Int={...}
 def findCell(c:Coordinates)(implicit b:Board):Cell={...}
}

A Jak chcemy mieć więcej komórek to też łatwo idzie :

trait GameOfLifeWithZombieCells extends GameOfLifeWithStandardCells{
 case class ZombieCell() extends GameCell
 def zombieNeighbours(c:Coordinates)(implicit b:Board):Int
 
 override def evolve(c:Coordinates):Cell=(findCell(c),lifeNeighbours(c),zombieNeighbours(c)) match {
  case (DeadCell(),3,3)=>ZombieCell()
  case (LiveCell(),2,2)=>ZombieCell()
  case (LiveCell(),3,_)=>LiveCell()
  case _ => DeadCell()
 }
}

object ZombieGame3D extends GameOfLife3D with GameOfLifeWithZombieCells{
 def lifeNeighbours(c:Coordinates)(implicit b:Board):Int={...}
 def findCell(c:Coordinates)(implicit b:Board):Cell={...}
 def zombieNeighbours(c:Coordinates)(implicit b:Board):Int={...}
}

Jakie ograniczenie?

No i cała magia coderetreat polega na tym aby za bardzo nie wchodzić w teorię ale by dać ograniczenie i pozwolić każdemu na własne skórze odczuć uwagi i wnioski. Tutaj można dać "wszystkie typy abstrakcyjne" albo "jeden typ abstrakcyjny na trait" ale to na razie jest jedna wielka improwizacja z mojej strony także zobaczymy co przyszłość przyniesie.

Z kronik "Maintenance City"

Przypomniało mi się zabawne zdarzenie z 2011 kiedy jeszcze nie prowadziłem a tylko uczestniczyłem w CR. Siedzimy sobie i robimy pair programming no i nagle koleś się zawiesza. Pytam "co się dzieje" a ten prawie ze łzami w oczach "o kurde od roku chyba nie robiłem nowej klasy w Eclipse". No to pozdrowienia dla wszystkich we wspólnej walce na awans z low cost centre utopia na to co tam jest następne.

środa, 16 lipca 2014

Tragedia wspólnego kodu

Scenka1

Facet idzie sobie przez las i napotyka gościa wycinającego siekierą choinki
- Panie co pan robisz! Dlaczego dewastujesz las?
- Nie dewastuję lasu tylko zaciągam dług leśny. Będą święta, na choince powieszę bombki to rodzina będzie zadowolona. No a później posadzę nowe choinki w to miejsce.
- A kiedy to zrobisz?
- no za trzy sprinty...

Scenka2

Facet idzie sobie przez osiedle i spotyka gościa, który wywala gruz do piaskownicy
- Dlaczego dewastujesz plac zabaw?
- Nie dewastuję placu zabaw tylko zaciągam dług osiedlowy. Kładę teraz nową boazerię na ścianę i musiałem skuć trochę. Ale jak już skończę to obiecuję, że ogarnę cały plac zabaw.
- a kiedy to zrobisz?
- no za trzy sprinty...

Scenka3

Facet idzie sobie po piętrze w firmie i patrzy a tu gościu piszę metodę na 500 linijek i 20 ifów
- Panie co pan robisz! Dlaczego dewastujesz kod?
- Nie dewastuję kodu tylko zaciągam dług techniczny. Jest koniec sprinta i trzeba domknąć velocity bo inaczej nam targety polecą. Ale jak już to ogarniemy to potem poprawię.
- a kiedy to zrobisz?
- no za trzy sprinty...

Obecna metafora

Obecnie panująca metafora długu technicznego jest na pewno lepsza od braku jakiekolwiek metafory jako, że przecież dzięki niej w końcu ludzie nie do końca techniczni obdarowani ogromną mocą decyzyjną mogą w pewnych ramach mentalnych zrozumieć, że czasami jak działa to nie znaczy, że jest dobrze (a być może jest dobrze ale dobrze nie będzie).

Jednak pojawia się jednocześnie korpo-zagwostka : bo jak tu pokazać na zebraniu rady wydziałowej, że zaciągnęło się dług pragmatycznie, zainwestowało go pragmatycznie , spłaciło pragmatycznie i liczy zyski również pragmatycznie?

Oczywiście istnieje klasyczny algorytm rozwiązywania tego typu problemów : zmierz cokolwiek co generuje jakiś wykres i jedziemy z targetami - może na początek 120% pokrycia kodu?

Najlepszy tekst jaki do tej pory słyszałem to target : "zmniejszyć dług techniczny o 50%". No i oczywiście programiści nie mogą się z tego głośno śmiać bo mają do spłacenia kredyty, dziecku trzeba zeszyty kupić i psa zaszczepić. No i leci kabarecik. Kilka lat temu sam pisałem testy jednostkowe do 500 linijkowych metod z 30 ifami, które to metody napisali "ludzie, którzy już tu nie pracują" - target spełniony, pokrycie kodu zwiększone do 60% - nieustające pasmo sukcesów trwało.

Gdyby to nie wynikało jasno z powyższych rozważań to model "długu technicznego" pomimo swoich nieocenionych walorów edukacyjnych wydaje się na pod pewnymi względami a może i na dłuższą metę zwyczajnie ku*wa lewy. Jednocześnie istnieje być może coś ciekawszego, coś z zupełnie innej beczki...

Jak to wyszło na wyspach wielkanocnych

Cała historia jest opisana szczegółowo tutaj : Zjebka na wyspach wielkanocnych - a tak w skrócie to:

  1. Pomiędzy 400AD a 900AD na wyspy wielkanocne przybywają kontraktorzy z Polinezji i zaczynają nowy greenfield project
  2. Z czasem dzielą się na 12 teamów i zaczynają projekty budowy zajebistych takich dużych pomników głów
  3. Każdy z 12 managerów chce jebnąć najfajniejszą głowę z kamienia - bo generalnie im większa głowa wyrzeźbiona tym więcej respektu na wyspie,lepsze bonusy świąteczne i miejsce na parkingu bliżej wejścia.
  4. Aby przytachać kamienie na plaże gdzie znajdują się stanowiska pracy - używano ściętych drzew jako rolek (taki framework).
  5. Projekty idą zgodnie z planem - kolejne głowo-kamienie milowe są budowane
  6. Mija kilka wieków i na wyspie nie ma już żadnych drzew a co za tym idzie cały ekosystem poooszedł w pizdu.
  7. Na początku szesnastego wieku na wyspę przypływają Europejczycy i zastają garstkę ledwo żyjących mieszkańców (zdaje się, że około 3-10% populacji przetrwało)

A był to wstęp do szerszego zjawiska, które nazywa się..

Tragedia wspólnego pastwiska

Tutaj trzeba poruszać się ostrożnie bo powyższe zjawisko może być zbyt łatwo nad interpretowane "trzeba ludzi kontrolować bo się pozabijają" - co jak zobaczymy nie do końca o to tu chodzi

W 19 wieku angielski ekonomista zauważył, że jak chłopom dać wolną rękę to każdy maksymalizując swój zysk wprowadzi owce czy tam krowy na łąkę - a że każdy chce jak najbardziej nakarmić swoją zwierzynę to szybko wyeksploatują całe pole i będzie kicha dla wszystkich. Niby koleś miał trochę racji ale ważny jest tutaj kontekst sytuacji - otóż ci chłopi wcale nie byli właścicielami ziemi, z której korzystali. Lądem władał tzw. "Suweren" lub była zwyczajnie niczyja.

Własność prywatna - ale czyja?

Pod koniec 20 wieku kilku ekonomistów przypatrzyło się całej teorii i znaleźli pewną istotną zjebkę - otóż w orginale nie rozróżniano dwóch diametralnie różnych sytuacji : "korzystanie z ogólno dostępnego dobra" oraz "korzystanie z dobra będącego własnością wspólnoty". Okazuje się, że w tym drugim przypadku wspomniane wspólnoty ludzi jakoś same rozkminiały zasady korzystania z lasu,jeziora czy łąki. Rozwiązywali oni sami także tzw. "Efekt gapowicza": (Syndrom wesołego Cześka).

Jedna taka pani (Elinor Ostrom) przeprowadziła nawet dogłębne badania i obserwacje, w trakcie których zauważyła następujące niepokojące zjawisko :

  • Jest sobie las a obok lasu wieś
  • Mieszkańcy wsi korzystają z dóbr leśnych a jednocześnie jak ktoś przekombinuje to dostanie parę sęków gdyż mieszkańcy wsi tak po trochu uważają las za część wspólnoty
  • W państwie brakuje kasy
  • Las staje się tzw. "własnością publiczną"
  • Ludziom narzuca się reguły i mają w zasadzie po trochu już w dupie czy ktoś je łamie - od tego momentu to już bardziej sprawa indywidualnej etyki aniżeli społecznego współużytkowania wspólnego dobra

I kilka cytacików :

Ostrom demonstrated that, within communities, rules and institutions of non-market and not resulting from public planning can emerge from the bottom up to ensure a sustainable, shared management of resources, as well as one that is efficient from an economical point of view. Besides the village of Törbel, Ostrom shows examples of common lands in the Japanese villages of Hirano and Nagaike, the huerta irrigation mechanism between Valencia, Murcia and Alicante in Spain, and the zanjera irrigation community in the Philippines.

The first condition for the institutional basis of the success of these mechanisms is the clarity of the law (Who can do what? What can one not do? Who punishes whom? And how?). In addition to being clear, the rules must be shared by the community. This is why another essential element of self-government is the establishment of methods of collective and democratic decision-making, able to involve all users of the resource.

Furthermore, the mechanisms of conflict resolution must be local and public, so as to be accessible to all individuals of a community. Besides mechanisms of graduated sanctions, a mutual control of the resource among the users themselves must be established.

Więcej tutaj : http://www.aei.org/article/economics/elinor-ostrom-and-the-solution-to-the-tragedy-of-the-commons/

Jak to się ma do jakości kodu

Kilka obserwacji z życia wziętych :
  • Czasem - zwłaszcza na początku projektu - programista może skończyć prace nad taskiem szybciej implementując totalnie zjebany kod bez testów
  • Niska jakość kodu jebnie w cały zespół w przyszłości
  • Jeśli zespól jest poddany pseudonaukowym korpo-praktyką w stylu ocena performensu "risorsa" przez menadzera to pojawia się dodatnie sprzężenie zwrotne pomiędzy szybszym skończeniem taska a nagrodą
  • Jeśli zespół (a czasami trudno mówić o zespole bo ludzie są oceniani indywidualnie) ma narzucone reguły kodu to tak mentalnie z założenia widzi właściciela tych reguł poza zespołem
  • Jeśli właściciel kodu jest poza zespołem to można go traktować jako "dobro ogólno dostępne" z narzuconymi restrykcjami
  • Jest jeden wyjątek gdzie takie narzucone restrykcje są dobre ale jednak w praktyce widzę, że tworzy się często "korporacyjne reguły kodu" i wali po całości unifikacją. To tak jakby każdemu w korpo kazać nosić ten sam rozmiar buta.

Zastanówmy się nad taką metaforą - kod projektu jako wspólne dobro - ale wspólne dobro czego konkretnie? W lesie są drzewa, w morzu są ryby a na łące trawa (dla krów) - co jest w kodzie? W kodzie są szybkie postępy pracy, sukcesy projektowe, potencjał awansu i abstrakcyjny banknot pieniężny. Teoretycznie ktoś waląc haka na kodzie zagarnia te wszystkie rzeczy dla siebie utrudniając jednocześnie osiągnięcie tych samych rzeczy innym. - i jeszcze raz - Syndrom wesołego Cześka

A teraz zastanówmy się co by się stało gdyby tak zespół stał się właścicielem kodu i żaden szczebel menadżerski się w to nie wpierdalał? Tutaj nawiążemy do tego tekstu pisanego italikiem kilka akapitów wyżej :

  • Ostrom demonstrated that, within communities, rules and institutions of non-market and not resulting from public planning can emerge from the bottom up to ensure a sustainable, shared management of resources - czy zespół może "oddolnie" stworzyć reguły użytkowania kodu? Ano może
  • The first condition for the institutional basis of the success of these mechanisms is the clarity of the law (Who can do what? What can one not do? Who punishes whom? And how?). In addition to being clear, the rules must be shared by the community. - wspólne reguły kodu na które każdy w zespole się godzi? Tutaj będzie zaraz jedno ALE
  • Besides mechanisms of graduated sanctions, a mutual control of the resource among the users themselves must be established. - wypisz wymaluj code rewiu.

Ten model znacznie bardziej podoba mi się od porównywania pisania kodu do zaciągania "długu". Niby jest fajnie bo można do góry wysłać wiadomość, że teraz trzeba trochę posprzątać bo kilka placków gówna powstało z okazji dedlajnu. Z drugiej jednak zjawisko pracy z "długiem" raz, że sprawia, że niektórzy myślą, iż rozumieją z czym pracują a tak jest niekoniecznie. No a dwa to pomija ważne aspekty pracy grupowej jak choćby problem radzenia sobie z pasażerem an gapę - i po raz trzeci - Syndrom wesołego Cześka

Jedno małe duże ALE

Jest jedna taka sytuacja w życiu kiedy nawet najbardziej zagorzały przeciwnik wszelkich ograniczeń da się poprowadzić za rękę - jest to tzw "tutorial level" - kiedy nasza wiedza jest zerowa i zwyczajnie czujemy każdą komórką ciała, że czerpiemy wartość z wykonywania poleceń.

Tak jest oczywiście w teorii bo w praktyce działa też inny ciekawy efekt. Otóż np. taka Łódź do niedawna była głównie "maintenace city" i wiele osób siedziało po kilka lat wklejając tu i tam linijkę kodu aby zadziałało (R&D w Łodzi - Refactor and Deployment). Jak ktoś robi takie rzeczy kilka lat i widzi w ogłoszeniach o pracę "senior java developer wymagane 3 lata doświadczenia" to ma prawo pomyśleć , że on jest właśnie takim senior developerem i cały proces odbywa się raczej nieświadomie w ramach tzw. "warunkowania społecznego".

I teraz jak np. ktoś przychodzi do tego ludzika i próbuje go przekonać, że np trzy zagnieżdżone fory z piętnastoma ifami w środku nie są najlepszym pomysłem, to następuje wewnętrzny konflikt pojęciowy pomiędzy "uważam się za doświadczonego developera" a "okazało się, że moim kodem można straszyć dzieci jak nie będą chciały jeść zupki". Prowadzi to do tego, że wiedza o tym jak konkretne decyzja w kodzie wpływają na konkretne metryki i co tak naprawdę zyskujemy/tracimy poprzez stworzenie abstrakcji lub zagnieżdzenie ifa - ta wiedza niekiedy musi być wtłoczona w zespół lekko na siłę. Zakładając, że problem Cześka został wyeliminowany a reszta ludzi wykazuje "oświecony egoizm" -> http://en.wikipedia.org/wiki/Enlightened_self-interest - wtedy wszystko się ułoży. Oczywiście jestem świadom tego, że na mnie działają te same procesy - od czasu "funkcyjnego przebudzenia" staram się znajdować krytykę do każdego konceptu który badam.

To podobnie jak kiedyś miałem taką historię jak się dowiedziałem, że nie należy dokarmiać dzikich kaczek chlebem bo im puchnie to w brzuchy i implodują czy coś takiego. Czyli ludzie mogą chcieć dobrze ale z powodu braku wiedzy robią źle.

Także na dzień dzisiejszy : CZYSTY KOD=MODEL KODU JAKO DOBRA WSPÓLNOTY+ZESPÓŁ WŁAŚCICIELEM KODU + ODPOWIEDNIA ILOŚĆ NAPOMPOWANEJ WIEDZY O JAKOŚCI KODU

Epilog

Na pewno warto model kodu jako ograniczonego dobra wspólnego otwiera nowe ścieżki nazwijmy to "badań" aniżeli metafora "długu" i warto by się przyjrzeć schoćby temu : http://en.wikipedia.org/wiki/Socio-ecological_system - i przestać udawać, że problem jakości kodu sprowadza się do kilku metryk w sonarze i, że się poprawi po trzecim sprincie. A no i akurat na courserze zaczyna się kurs "social psychology" - https://class.coursera.org/socialpsychology-002

Trochę linków :

poniedziałek, 30 czerwca 2014

Play - bonusy do ćwiczeń

Poniżej lista małych tematów, które można dorzucać do większych ćwiczeń jak jakimś cudem zostanie trochę czasu.

Globalne info o braku strony

To je dobre bo w innych frameworkach ten mechanizm jest na porządku dziennym.

//Global.scala - w katalogu app- będzie warning o  tym by nie dodawać do domyślnej paczki.
object Global extends GlobalSettings {

  override def onStart(app:Application){
    Logger.info("startujemy ku*wa") //to tak dla bajery
  }

  override def onHandlerNotFound(request: RequestHeader) = {
    Future.successful(NotFound(
      views.html.nieZnalazlem(request.path) 
    ))
  }
}

//nieznalazlem.scala.html
@* nieZnalazlem Template File *@
@(param: Any)

<h1>Nie ma takie strony ku*wa</h1>

Taki niby Quartz

Tutaj jest pierwszy kontakt z Akka.

//Global.scala
override def onStart(app:Application){
    Logger.info("startujemy ku*wa")
    
    import play.api.Play.current
    import scala.concurrent.duration._
    import scala.concurrent.ExecutionContext.Implicits.global
    
    val quartzActor=Akka.system.actorOf(Props(new quartz.QartzActor()))
    Akka.system.scheduler.schedule(0.seconds,30.seconds,quartzActor,RobCos())
  }

class QartzActor extends Actor{
 def receive={
   case RobCos()=>println("robie cos")
   case _ =>println("robie nic")
 }
}

case class RobCos()

Zabawniejszy konfig

Konfig w Playu jest bardzo wygodny w użyciu i ma kilka ciekawych sztuczek. Są takie niby przestrzenie nazw dla propertasów oraz sklejanie propertasów, które fajnie działa przy sklejaniu ścieżek. Tu dla sportu stworzymy sobie ścieżkę z warzywa.

//application.conf
warzywo: {
 kolor=zielony,
 smak=kwaśny
}

property=${warzywo.kolor}/resztaSciezki

//html
@play.api.Play.current.configuration.getString("warzywo.kolor")
<br/>
@play.api.Play.current.configuration.getString("property")

Minimalizacja javaskryptu

Ten tamat znacznie się rozwinął w Play 2.3 za sprawą sbt-web (chyba) także tutaj uboższa wersja dla wersji 2.2

w app stworzyć paczkę assets.js

//test.js
function dzialaj(a){
 var tekst="aaa"
 console.log(tekst)
}

//_notCompiled.js
function dzialaj2(a){
 var tekst="aaa"
 console.log(tekst)
}

Teraz trzeba odświeżyć stronkę bo jakoś sam z siebie tych rzeczy nie kompiluje i mamy

//test.min.js
function dzialaj(a){var tekst="aaa";console.log(tekst)};

//_notCompiled.js
function dzialaj2(a){
 var tekst="aaa"
 console.log(tekst)
}

I to to nawet nam kompiluje javascript i sprawdza składnie! Bardzo ładnie!

No i to były właśnie takie krótkie małe tematy.

niedziela, 22 czerwca 2014

Play - formularze - fotki i spostrzeżenia

Najpierw komercha

Pizza sama się nie kupuje - więc jest i logo firmowe.
Obsługa wyjątków bez wyjątków.
W sumie trzeba było zrobić dwa zdjęcia - najpierw jedno z lewej strony - a w drugim wszyscy przechodzą na prawo i zakładają dla niepoznaki ubrania na lewą stronę - frekwencja 200%.
A tak się obsługuje formularze

Co następne

Teraz po wakacjach najpewniej :

  1. Powtórka wprowadzenia - teraz być może z Play 2.3
  2. Wystawianie RESTa przy pomocy JSONa i konsumpcja przy pomocy webservisów (żeby nie bawić się w javaskrypty)
  3. Integracja Playa z Akka

Spostrzeżenie

Aby w tym wpisie pojawiło się coś poza autopromocją - czas na ciekawe spostrzeżenie jakie pojawiło się w trakcie warsztatów.

Sytuacja wygląda tak : są jakieś okoliczności i pojawia się reakcja na te okoliczności. Reakcja wtapia się w światopogląd i nawet gdy już okoliczności się zmieniają i dana reakcja jest już być może niepotrzebna to już zdążyła stać się dogmatem, którego się nie kwestionuje bo nie. Dogmat w tym przypadku brzmi "nazwy zmiennych muszą zdradzać ich przeznaczenie czyli w skrócie - być czytelne". To było zbawianie gdy trzeba było pracować z 10000 liniami "managero-helkperów" w Javie (jeśli dla kogoś nie jest to czas przeszły to współczuję) ale czy jest to aktualne gdy nie pracuje się z okaleczonym kodem?

I teraz nowa sztuczka z kołczingu - aby uratować się przed flejmwarem należy sytuacje przedstawić nie w kontekście "co jest lepsze" ale raczej "czy zabawa w szukanie dziur w dogmacie da mi nowe możliwości i rozwinie moją osobowość abym stał się bardziej kompletnym bytem"- ano dokładnie tak!

Inspiracja tym tematem pojawiła się u mnie po lekturze jakiegoś bloga, którego teraz nie mogę znaleźć ale generalna koncepcja polegała na tym aby intencje zmiennej przekazywać nie przez jej nazwę ale przez jejże typ.

I teraz zerknijmy na dwie deklaracje metodki, która tworzy mi taki mały parserek do zapisywania pliku z formularza na dysku :


//po staremu
  def saveMultipartFile(multipartFileName:String,pathOnDisc:String)(data:MultipartFormData[TemporaryFile])=
//po nowemu
  def saveMultipartFile(m:MultipartFileName,p:PathOnDisc)(data:MultipartFormData[TemporaryFile])=
No i jaki widzę problem z pierwszą wersją? Ano taki jak z dokumentacją w wordzie - pisać można tam sobie wszystko co się komu podoba. I tka naprawdę to jaka jest pewność, że pierwszy parametr to faktycznie nazwa pliku a nie nazwisko panieńskie babci? W trakcie refaktoringu kod się zmieni a nazwa zostanie taka sama i dupa.

W drugim przypadku też 100% pewności nie ma ale raz, że w trakcie refaktoringu typy - czyli zwyczajne nazwy klas - zmienia IDE a nie JAnek programista z palca - a dwa, że jako String to mogę sobie przekazać co chcę i się zesra w Runtime. No i jest jeszcze trzecia strona medalu. Tak się wywołuje tę metodkę w kodzie :

val savingFileParserFunction=saveMultipartFile(MultipartFileName("file"), PathOnDisc("/tmp/testy"))_
I tutaj od razu w kodzie widać co jest co bez potrzeby wyświetlania Popapów z docami. Dla porównania w wywołaniu klasycznym :
val savingFileParserFunction=saveMultipartFile("file", "/tmp/testy")_
Tutaj dla odmiany raczej trzeba będzie zajrzeć do doca.

Na Code Retreat jest takie ćwiczonko gdzie należy każdą koncepcję w systemie przedstawić przy pomocy tzw. "klasy domenowej" czyli np. współrzędna jest typu "Wspolrzedna" a nie typu "Integer". No i w Javie pisanie tych wszystkich klas jest niby ok ale czułem się wewnętrznie, że w życiu raczej tego nie zastosuje bo wymaga to zbyt dużo prądu. W Scali sobie gdzieś w module trzasnę :

case class MultipartFileName(name:String) extends AnyVal
case class PathOnDisc(path:String) extends AnyVal
I już.

I jeszcze jedna ciekawa rzecz

Ten mały fragmencik :

extends AnyVal
Podobno sprawia, że kompilator w trakcie kompilacji "rozpakuje" typ i stawia w jego miejsce String wywalając wrappera. Czyli nie ma narzutu na pamięć kosztem tam chwilę dłuższej kompilacji. Chyba tak to działa.

I najważniejsze, żeby nie stawiać tej koncepcji w opozycji do tego co się wie i w co się wierzy ale spróbować chwilę się nią pobawić. Być może będzie to nowy bodziec do rozwoju i usprawnienia naszej pracy? A może po chwili się go wyrzuci i wróci do dotychczasowych metod? Najważniejsze to traktować to jako ćwiczenie a nie spór o to co lepsze. Po to też między innymi kasuje się kod w Code Retreat - aby pobawić się być może niewygodnymi pomysłami ze świadomością, że i tak to pójdzie do kibla i zostaną jedynie przeżycia w mojej czy twojej głowie.

Coś z zupełnie innej beczki

Bluzganie redukuje ból oraz zmniejsza wewnętrzne napięcie :

A co najważniejsze bluzganie buduje team spirit! Wiarygodności tych badań sprawdzać nie muszę gdyż weryfikuję je od lat trzydziestu. Ewentualnie jak ktoś był chowany pod kloszem i życia doświadczył tylko z odcinków Drużyny A to może zerknąć na : http://filing.pl/ilustrowany-podrecznik-dla-mlodziezy-dobrze-wychowanej/

niedziela, 1 czerwca 2014

Play Formularze - opis ćwiczeń

Poniżej opis drugiego modułu (słowo "moduł" brzmi tak zajebiście) ćwiczeń z Play Framework. Pierwszy moduł pojawił się w dwóch częściach :

Poniższy materiał to krótki zbiór ćwiczeń opracowany na podstawie tutoriali i książek - przystosowany do nauki Playa w trakcie 2-3 godzinnego spotkania z przerwą na pizze czy na jakieś zdrowsze jedzenie. Program nie jest jeszcze przetestowany w życiu ale powinien płynnie pokazać uczestnikom epickość formularzy w Play gdzie testować można po prostu wszystko. Także miłej zabawy z lektury i ćwiczeń.

Gdy to tak sobie piszę jest jeszcze jedno wolne miejsca na spotkanie także jak ktoś ma ochotę niech się śmiało dopisuje -> Link do spotkania na meetupie

Co przed startem

Aby nie zamulać już na samym początku dobrze by było gdyby każdy miał już rozpakowany Play przez zipa czy activatora. Dodatkowo fajnie przypomnieć sobie podstawy w tym celu każdy musi :

  • Zrobić nową aplikację
  • Import do Eclipse czy co kto tam używa
  • Stworzyć nowy widok jako ćwiczenie - czyli kontroler,mapowanie i szablon
  • Zostawić stronę index bo tam się wyświetla link do dokumentacji
  • Jak ktoś używa eclipse to poustawiać sobie zgodnie z http://scala-ide.org/docs/tutorials/play/

Wio

Dobrze mieć pod ręką otwartą lokalną dokumentację by powklejać importy i takie tam :

import play.api.data.Forms._
import play.api.data._

Tutaj zastanawiałem się czy większą wartość edukacyjną będzie miał jakiś zabawny przykład czy też coś generycznego jak User. Trzaśniemy coś z jedzeniem to może ludzie nauczą się patrzeć na to co jedzą (no i będzie bardziej różnorodnie bo nie zerżniemy przykładu prosto z dokumentacji).

case class Meal(name: String, calories: Int)

val mealForm=Form(mealMapping)
  
lazy val mealMapping=mapping(
    "name"->text,
    "calories"->number
)(Meal.apply)(Meal.unapply)

Deklaracja mappingu na zewnątrz wydaje mi się czytelniejsza a lazy jest potrzebne bo bez tego czasami się wywala gdzieś tam wewnątrz inicjalizacja Forma.

Tak czy inaczej mając tylko deklaracje mappingu i forma można teraz pokazać na testach, iż testować można wszystko.

    "bind data to meal mapping" in {
      val data=Map("name"->"hamburger","calories"->"5000")
      val mealData=Application.mealMapping.bind(data)
      mealData.isRight must beTrue
    }
    
    "bind data to meal mapping with errors" in {
      val data=Map("name"->"hamburger","calories"->"5000b")
      val mealData=Application.mealMapping.bind(data)
      mealData.isRight must beFalse
    }
    
    "bind data to meal form with error" in {
      val data=Map("name"->"hamburger","calories"->"5000b")
      val mealData=Application.mealForm.bind(data)
      mealData.errors must have(_.key=="calories")
    }

I ogólnie fajne i czytelne asercje są. Tutaj można zrobić szybko skok do Scali i wytłumaczyć co to je to Left i Right - będzie to też dobry wstęp do walidacji formularzy.

Either

Ten temat wywołuje u niektórych dużo zabawy za pierwszym razem bo w Javie informacja o tym, że coś może się nie udać zazwyczaj nie jest zawarta w typie. I jak mamy np. coś takiego:

def divide(a:Int,b:Int)=a/b       //> divide: (a: Int, b: Int)Int
divide(4,2)                       //> res0: Int = 2
divide(4,0)                       //> java.lang.ArithmeticException: / by zero

To przez zero dzielić nie można. I można zrobić tak :

def divide(a:Int,b:Int)={
 if(b==0) throw new IllegalArgumentException("cos tam")
 a/b
}

Ale można też :

def divide(a:Int,b:Int)=
       if(b==0) None else Some(a/b)

divide(4,2).map(_+1)              //> res0: Option[Int] = Some(3)
divide(4,0).map(_+1)              //> res1: Option[Int] = None

I tutaj ludzie, którzy robią w Javie od kilku lat maja jakaś dziwną radochę, ze typ nazywa się "Cośtam" albo "Nico". Pewnie będą jakieś pytanie "a po ch.." to w ogóle jest. Zycie pokaże co z tego wyjdzie. Tak czy inaczej mając Option chyba łatwiej będzie wytłumaczyć Either.

def divide(a:Int,b:Int)=
   if(b==0) Left(new IllegalArgumentException("cos tam"))
   else Right(a/b)           //> divide: (a: Int, b: Int)Product with Serializable with scala.util.Either[Ill
                                                  //| egalArgumentException,Int]
  divide(4,2).isRight               //> res0: Boolean = true
  divide(4,0).isRight               //> res1: Boolean = false

To będzie przykład w kontekście testów, które widzieliśmy chwilę wcześniej. A w kontekście obsługi forma, której jeszcze nie było - poniższe trzy linijki

val result=divide(4,2)
val funkcjaBiznesowa=(x:Int)=>x+1
result.fold(throw _, funkcjaBiznesowa)          //> res2: Int = 3

No i tutaj chyba każdy powinien zajażyć, że funkcjaBiznesowa nie jest sponiewierana przez wyjątki co ułatwia testowanie - z drugiej strony pewnie znowu ludzie będą śmiać się z podkreślinika throw _

No to robimy formularz

@(mealForm:Form[Meal])
<html>
<head>
<title>Formularz</title>
</head>
<body>

@import helper._

@form(action=controllers.routes.Application.submitForma){
@inputText(mealForm("name"),'id->"name")
@inputText(mealForm("calories"))
<input type="submit">
}

</body>
</html>

I zmiana w routach oraz kontrolerze :

//routes
POST   /submitForma    controllers.Application.submit()

//Application
def form = Action {
    Ok(views.html.formularz(mealForm))
  }
  
  def submitForma=TODO

Czas na obsługę POSTA - tutaj wąłśnie przydaje się wiedza z ćwiczenia "Either".

def submitForma = Action { implicit request =>
    mealForm.bindFromRequest.fold(
      badForm => BadRequest(views.html.formularz(badForm)),
      _ => Ok("mealOK")
    )

  }

Na razie bez styli wygląda to koślawo ale na prostszych przykładach łatwiej się uczyć.

Dokladniejsza walidacja

Na razie walidacja jest uboga dlatego dodamy sobie kilka parametrów aby ograniczyć to co można wprowadzić i do tego wrzucimy trzecie pole opcjonalne aby pokazać jak ładnie wszystko nam się skomponuje. Warto dodać, że wszystkie validacje są w obiekcie Forms

lazy val mealMapping = mapping(
    "name" -> nonEmptyText(3, 10),
    "calories" -> number(1000, 8000),
    "description" -> optional(text)
  )(Meal.apply)(Meal.unapply)


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

I w ten czas jest dobry moment aby uaktualnić testy o sprawdzenie obsługi pełnego żądania http.

//routes
POST   /submitForma    controllers.Application.submitForma()

//testy
"submit user form successfully" in new WithApplication{
     val formRequest=FakeRequest(POST,"/submitForma").withFormUrlEncodedBody("name"->"hamburger","calories"->"5000")
     val result=route(formRequest).get
     status(result) must equalTo(OK)
    }
    
    "return bad request when form data is missing" in new WithApplication{
      val formRequest=FakeRequest(POST,"/submitForma").withFormUrlEncodedBody("name"->"hamburger")
      val result=route(formRequest).get
      status(result) must equalTo(BAD_REQUEST)
    }

No i technicznie nawet nie musieliśmy odpalać formularza aby wiedzieć, że wsio działa :D. Teraz czas na jeszcze jeden patent - globalna walidacja!

//Application.scala
lazy val mealMapping = mapping(
    "name" -> nonEmptyText(3, 10),
    "calories" -> number(1000, 8000),
    "description" -> optional(text)
  )(Meal.apply)(Meal.unapply) verifying("zle dane tak ogolnie", mealData=>mealData.calories>2000 && mealData.name!="Warzywo")


//test
 "bind data to user form with global error" in {
     val data=Map("name"->"Warzywo","calories"->"5000")
     val mealData=Application.mealForm.bind(data)
     mealData.errors must have(_.message=="zle dane tak ogolnie")
    }

//html
@if(mealForm.hasGlobalErrors) {
<ul>
@for(error<-mealForm.globalErrors){
<li>@error.message</li>
}
</ul>
}

I można też verifying pojedyncze pola -

"name"->text(3, 10).verifying("can not be Warzywo", _!="Warzywo")

No i pewnie padnie pytanie jak te napisy pod polami pozmieniać. To można na szybko tak :

@inputText(mealForm("calories"),'_showConstraints->false,'_help->"moj koment")

Ale zapewne to nie wystarczy i czas przejść do definiowania własnych komunikatów.

Custom messages

I teraz jest taka opcja - odpowiednie komentarze muszą pojawić się w pliku conf/messages, którego jeszcze nie ma. Wszystkie możliwe komunikaty zaś znajdziemy tam gdzie rozpakowaliśmy archiwum playa - $PLAY_HOME/framework/src/play/src/main/resources/messages

Co by ni szukać poniżej pełna lista - za długa to ona nie jest

# Default messages

# --- Constraints
constraint.required=Required
constraint.min=Minimum value: {0}
constraint.max=Maximum value: {0}
constraint.minLength=Minimum length: {0}
constraint.maxLength=Maximum length: {0}
constraint.email=Email

# --- Formats
format.date=Date (''{0}'')
format.numeric=Numeric
format.real=Real

# --- Errors
error.invalid=Invalid value
error.invalid.java.util.Date=Invalid date value
error.required=This field is required
error.number=Numeric value expected
error.real=Real number value expected
error.real.precision=Real number value with no more than {0} digit(s) including {1} decimal(s) expected
error.min=Must be greater or equal to {0}
error.min.strict=Must be strictly greater than {0}
error.max=Must be less or equal to {0}
error.max.strict=Must be strictly less than {0}
error.minLength=Minimum length is {0}
error.maxLength=Maximum length is {0}
error.email=Valid email required
error.pattern=Must satisfy {0}

error.expected.date=Date value expected
error.expected.date.isoformat=Iso date value expected
error.expected.jodadate.format=Joda date value expected
error.expected.jsarray=Array value expected
error.expected.jsboolean=Boolean value expected
error.expected.jsnumber=Number value expected
error.expected.jsobject=Object value expected
error.expected.jsstring=String value expected
error.expected.jsnumberorjsstring=String or number expected
error.expected.keypathnode=Node value expected

error.path.empty=Empty path
error.path.missing=Missing path
error.path.result.multiple=Multiple results for the given path

Walnijmy sobie takie zestawik jak poniżej i zerknijmy co wyjdzie.

#forms
constraint.required=wymagane
format.numeric=musi być numerem

custom template

Czas by bliżej przyjrzeć temu co jest generowane przez te funkcje "@inputText" - ...i to zrobimy na zajęciach a nie tutaj. W tym miejscu po prostu poznamy patent na zmianę domyślnej struktury html jeśli nam nie pasuje.

// tworzymy template playowy z htmlem
@* myFormField Template File *@
@(elements: views.html.helper.FieldElements)

<div class="formGroupMoje">
<label for="@elements.id"></label>
<div class="formInputMoje">
@elements.input
</div>
</div> //formatter pola z wykorzystaniem szablonu object MyFormConstructor { import views.html.helper.FieldConstructor implicit val myFields = FieldConstructor(myFormField.f) } // jest implicit to sam import w stronie forma wystarczy @import helper._ @import views.MyFormConstructor._

I od razu wygląd forma się zmienił. Czy na lepsze? Każda zmiana jest na lepsze poza zmianami na gorsze.

upload pliku

To ćwiczenie jest w całości zerżnięte z książki "Play for Scala developers". Wygodnie jest zadeklarować najpierw
//Application
def fileUpload=TODO
//routes
POST   /uploadFile     controllers.Application.fileUpload()
bo wtedy kompilator nie pluje się przy używaniu backward routing.
<form action="@routes.Application.fileUpload" method="post" enctype="multipart/form-data">
<input type="file" name="image">
<input type="submit">
</form>
I tutaj ważna i ciekawa rzecz - po raz pierwszy uczestnicy kursu spotykają się z parserami requestów - co będzie tematyką trzecich albo czwartych zajęć.
  def pokazPlikForm = Action {
    Ok(html.fileForm())
  }

  def fileUpload = Action(parse.multipartFormData) { request =>
    request.body.file("image").map { file =>
      file.ref.moveTo(new File("/tmp/image"),true)
      Ok(s"retrieved file ${file.filename}")
    }.getOrElse(BadRequest("file mising."))
  }

No i to tyle

No i to tyle