niedziela, 27 kwietnia 2014

Nauka PlayFramework - program edukacyjny część pierwsza

Poniżej program edukacyjny dla pierwszej części warsztatów "PlayFramework" (moduł 1 - "start"). Program wymyśliłem sobie sam i dla mnie subiektywnie jest dobry bo ująłem w nim swoje przeżycia z nauki Playa,Scali i w ogóle podejścia funkcyjnego. Czy będzie dobry dla wszystkich to się okaże...

Jak komuś się do czegoś ten program przyda to niech śmiało bierze i korzysta.

Pierwsze zetknięcie ze Scalą i programowaniem funkcyjnym

Program startuje z założeniem, że uczestnicy nie zetknęli się jakoś w dużym stopniu ze Scalą i programowaniem funkcyjnym. Niby Java8 powinna spopularyzować trochę to podejście ale nie zdziwię się jak masa osób będzie cały czas jechać w Javie4 czy Javie5 - taki urok miasta "low cost centrów".

Ćwiczenia będą się rozwijać a to co jest na tę chwilę opisałem pod linkiem : Playframework moduł 1 "Start" - opis warsztatów

Najpierw idzie godzinka scali dla zaznajomienia się z podejściem a później w drugiej części jedziemy z Playem.Poniżej bardziej szczegółowy opis pierwszej części.

Deklaracja funkcji jako zmiennej

Ten punkt ma za zadanie zaznajomić słuchaczy z sytuacja gdzie w zmiennej jest trzymana pewna logika, operacja a nie zaś same dane

val funkcja=(arg:Int)=>arg+1         //> funkcja  : Int => Int = |function1|
val dane=1                           //> dane  : Int = 1
 
funkcja(1)                           //> res0: Int = 2
funkcja(dane)                        //> res1: Int = 2

Kompozycje funkcji

Zadanie : zbudować funkcję z już istniejących z użyciem andThen oraz compose. Ta część ma pokazać siłę prostoty kompozycji funkcji. Wystarczy by zgadzały się określone typy funkcji aby można było je ze sobą składać. W OOP często atakuje nas specyficzny kontekst danego rozwiązania i trzeba uciekać do tworzenia abstrakcji, które mogą (ale nie muszą) zaciemniać rozwiązanie.

val pomnozPrzezDwa = (arg: Int) => arg * 2 //> pomnozPrzezDwa  : Int => Int = |function1|
  funkcja.andThen(pomnozPrzezDwa) //> res2: Int => Int = |function1|
  //można też bez kropek
  val dodajJedenIPomnozPrzezDwa = funkcja andThen pomnozPrzezDwa
  //> dodajJedenIPomnozPrzezDwa  : Int => Int = |function1|

  dodajJedenIPomnozPrzezDwa(1)
  //> res3: Int = 4

Metodę "compose" można wytłumaczyć na czymś bardziej z życia wziętym - na przykładzie klasy User (bo teraz chyba każdy system an ziemi ma klasę User). Może pojawić się pytanie co to jest to "case" - na tym etapie to jest raczej nieistotne i może wprowadzić niepotrzebny zamęt. Można powiedzieć, że to generuje z paczki hashcode, toStringa i kilka podobnych metodek.

case class User(email:String)
val validacjaEmaila=(email:String)=>email.nonEmpty
                                                  //> validacjaEmaila  : String => Boolean = |function1|
val emailUsera=(user:User)=>user.email    //> emailUsera  : poligon.User => String = |function1|
 
val validujeEmailUsera=validacjaEmaila.compose(emailUsera)
                                                  //> validujeEmailUsera  : poligon.User => Boolean = |function1|
validujeEmailUsera(User("aa@wp.pl"))      //> res0: Boolean = true
validujeEmailUsera(User(""))              //> res1: Boolean = false

Funkcje wyższego rzędu

Tutaj może pojawić się pierwszy kolaps czaszki gdyż standardowy programista Javy, który rzeźbi całe życie CRUDy raczej rzadko kiedy spotyka się z sytuacją przekazywania kawałka logiki jako argumentu do innego kawałka logiki. Niby to jest podobne do "Strategii" ale zazwyczaj w Springu czy czymś takim wszystko jest już wstrzyknięte. Co najwyżej można to porównać do przekazywania komparatora w Collections.

Szybka demonstracją może być sumowanie wszystkich liczb nieparzystych od 1 do 100. Drugim przykładem - bardziej przydatnym - wypisanie wszystkich maili Userow, które są krótsze niż 5 znaków.

val lista=1 to 100 toList                 //> lista  : List[Int] = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
                                                  //|  16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34,
                                                  //|  35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53,
                                                  //|  54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72,
                                                  //|  73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91,
                                                  //|  92, 93, 94, 95, 96, 97, 98, 99, 100)
val czyNieparzysta=(arg:Int)=>arg%2==0    //> czyNieparzysta  : Int => Boolean = |function1|
 val sumuj=(akumulator:Int,element:Int)=>akumulator+element
                                                  //> sumuj  : (Int, Int) => Int = |function2|
 lista.filter(czyNieparzysta).reduce(sumuj)//> res0: Int = 2550
Uproszczenie zapisu

W końcu dochodzi moment, w którym trzeba uczestnikom pokazać zapis z podkreślnikami - bez względu na wygodę takiego zapisu osoby niezaznajomione z tąże notacją mogą wykazać postawę "o ku*wa!". No i tutaj jest okazja wytłumaczyć kiedy nie trzeba deklarować typów bo kompilator może to wykminić z kontekstu.

//poniższe linie są równoważne
lista.filter(arg=>arg%2==0).reduce((akumulator,element)=>akumulator+element)  //> res0: Int = 2550
lista.filter(_%2==0).reduce(_+_)          //> res1: Int = 2550
Currying

To będzie potrzebne później przy templatach Playa. Tutaj pokazać tylko na szybko prosty przykład aby słuchacze zaznajomili się z faktem wykorzystywania kilku par nawiasów przy deklaracji funkcji. Według mnie prościej takie funkcje deklaruje się wychodząc od metody "def" ale tutaj dla porównania dwie wersje

def metoda(a:Int)(b:Int)=a+b              //> metoda: (a: Int)(b: Int)Int
val funkcja=(a:Int)=>(b:Int)=>a+b         //> funkcja  : Int => (Int => Int) = |function1|
 
val funkcjaZMetody=metoda(1)_             //> funkcjaZMetody  : Int => Int = |function1|
funkcjaZMetody(2)                         //> res0: Int = 3
 
val funkcjaZFunkcji=funkcja(1)            //> funkcjaZFunkcji  : Int => Int = |function1|
funkcjaZFunkcji(2)                        //> res1: Int = 3

Serwer jako funkcja

Teraz nastąpi połączenie dotychczasowych ćwiczeń z Playem.

case class Request(url:String)
case class Response(resultCode:Int)
 

val handler:(Request=>Response)=(request)=>request match{
 case Request("/jug/warsztaty") => Response(200)
 case Request("")=>Response(500)
 case wszystkoInne=>Response(404)
}                                         //> handler  : poligon.poligon.Request => poligon.poligon.Response = 
                                                  //| 

handler(Request("/jug/warsztaty"))        //> res0: poligon.poligon.Response = Response(200)
handler(Request(""))                      //> res1: poligon.poligon.Response = Response(500)
handler(Request("/poradnikSwiatlegoManagera"))
                                                  //> res2: poligon.poligon.Response = Response(404)

Mając powyższe możemy dojść do zapisu Action z Playa

def Action(handler:(Request=>Response))={
 println("jakies operacje na serwerze przed handlerem")
 val result=handler(Request("/aaa"))
 println("jakies operacje na serwerze po handlerze : "+result)
}                                         //> Action: (handler: poligon.poligon.Request => poligon.poligon.Response)Unit
 
 
Action(handler)                           //> jakies operacje na serwerze przed handlerem
                                                  //| jakies operacje na serwerze po handlerze : Response(404)
 
Action{handler}                           //> jakies operacje na serwerze przed handlerem
                                                  //| jakies operacje na serwerze po handlerze : Response(404)
 
Action{request=>
 println("inline function")
 Response(200)
}                                         //> jakies operacje na serwerze przed handlerem
                                                  //| inline function
                                                  //| jakies operacje na serwerze po handlerze : Response(200)
Podsumowanie

Powyższe ćwiczenia mają za zadanie wprowadzić uczestników w świat Scali i programowania funkcyjnego. Maja także za zadanie zsynchronizować wszystkich gdyż część ludzi kończy robotę tak, że może przyjść na 17 a część na 18.

Ćwiczenia są przewidziane na godzinę, Jeśli zajmą więcej czasu to można zacząć przeplatać je Playem. Za tydzień lub dwa lub trzy lub więcej opiszę część druga.

***

poniedziałek, 21 kwietnia 2014

Udawane Multiple Dispatch poprzez takie niby niewidzialne wizytatory

Dwie hierarchie

Z nauką nowych rzeczy jest trochę jak z oglądaniem reklam - nagle dowiadujesz się, że masz nowe potrzeby i to co było do tej pory przestaje wystarczać. A Coż może być takie fajne?. Gdy błądzący programista przedziera się przez materiały o obiektówce to zazwyczaj w sąsiedztwie słowa "Polimorfizm" znajduje się jakiś przykład gdzie kot i pies dziedziczą z klasy Animal czy coś w ten deseń.

Okazuje się, że te przykłady przedstawiają coś co nazywa się "single dispatch" i to coś ma ograniczenia, których nie ma bogatszy mechanizm zwany Double Dispatch lub Multi Dispatch. Może to dla większosci oczywiste ale mnie o tym w szkole nie uczyli.

A jeśli ktoś tak jak ja został niedopieszczony przez system edukacji to kod jak zwykle najlepiej pokaże o co chodzi. ( a jeśli ktoś jednak woli wikipedię to :

)

kod

Użyjemy dwóch hierarchii klas.
Pierwsza jest ludzka i zawiera

  • Klasę Human - nie chodzi humanistę tylko ogólnie o człowieka
  • Klasa Hipster dziedziczy z Human - Hipster to ktoś kto deklaruje swoją niezależność od "mainstreamu" głównie poprzez używanie mainstreamowych aplikacji społecznościowych
  • Klasa AntyHipster dziedziczy z Human - Antyhipster to ktoś kto jest takim hipsterem, że aż nie używa fejsbuka - wspomni o tym w przeciągu pierwszych 20 sekund rozmowy

Druga hierarchia to martwa natura i zawiera :

  • Klasę Drink - czyli napój
  • Klasa Vodka dziedziczy z Drink - czyli wóda
  • Klasa OrangeJuice dziedziczy z Drink - Litr soku pomarańczowego zawiera więcej cukru niż litr Coli --> ile cukru w cukrze

 

abstract class Human {
  def consume(drink: Drink):String
}
class Hipster extends Human{
  def consume(drink: Drink) = s"hipster consuming drink : $drink"
  def consume(drink: Vodka) = s"hipster consuming vodka : $drink"
  def consume(drink: OrangeJuice) = s"hipstewr consuming Orange : $drink"
}

class AntyHipster extends Human {
  def consume(drink: Drink) = s"antyhipster consuming drink : $drink"
  def consume(drink: Vodka) = s"antyhipster consuming vodka : $drink"
  def consume(drink: OrangeJuice) = s"antyhipster consuming Orange : $drink"
}


class Drink
class OrangeJuice extends Drink
class Vodka extends Drink

//deklaracje

val hipster = new Hipster
val antyhispter = new AntyHipster
val juice = new OrangeJuice
val vodka = new Vodka

Przypadek jeden Zero dispatch - mała elastyczność

Na samym początku przykład prosty jak cep. Metoda, która przyjmuje konkretną implementacje Human (Hipster) i konkretny napój (Vodka). Gdybyśmy chcieli obsłużyć wszystkie przypadki (Human,Drink) wtedy potrzebujemy 4 różne metody (bardzo niedobrze, bardzo bardzo źle)


def zeroDispatch(hipster:Hipster,vodka:Vodka)=hipster.consume(vodka)

zeroDispatch(hipster, vodka)              //> res0: String = hipster consuming vodka : Vodka

Trochę lepiej - Single dispatch

Co chcę zrobić : przekazać obiekt typu człowiek i ma się wywołać metoda z odpowiedniej implementacji. Czyli poniżej metoda spodziewa się po prostu Human i można tam przekazać i Hipstera i AntyHisptera - czyli klasyczny "Javowy Polimorfizm". Aby obsłużyć wszystkie kombinacje (Human,Drink) potrzebowałbym dwóch metod - po jednej na każdy napój.

def singleDispatch(human:Human,vodka:Vodka)=human.consume(vodka)
val hipster : Human= new Hipster
val antyhispter : Human = new AntyHipster

singleDispatch(hipster, vodka)            //> res0: String = hipster consuming drink : Vodka
singleDispatch(antyhispter, vodka)        //> res1: String = antyhipster consuming drink : Vodka

Multiple Dispatch - pierwsze podejscie z failem

I teraz ważne co chcę zrobić : chcę mieć jedną metodę która ani nie wie co to za Human ani co to za Drink i jak np. przekażę Hipstera i Vodkę to się automatycznie wywoła metoda z Hipstera, która przyjmuje Vodke.
Proba 3..2...1..

val hipster : Human= new Hipster
val antyhispter : Human = new AntyHipster
val vodka : Drink= new Vodka

def notWorkingMultipleDispatch(human:Human,drink:Drink)=human.consume(drink)
notWorkingMultipleDispatch(hipster, vodka)//> res0: String = hipster consuming drink : Vodka
notWorkingMultipleDispatch(antyhispter, vodka)//> res1: String = antyhipster consuming drink : Vodka
                                                  

No i Ch** bo jakby ktoś nie zauważył wywołana jest generyczna metoda "consume(Drink)". W Javie można zrobić takie nieładne "instanceof" albo Vizytatora. W Scali można zrobić Pattern Matching albo Vizytatora.

Ewentualnie można zrobić takiego "niewidzialnego" Vizytatora co zrobimy dla ćwiczeń teraz bo nie mam innego pomysłu na zakończenie tego artykułu

Rzeźbimy

abstract class Human {
  def consume[A](drink: A)(implicit dispatcher: DrinkMultiDispatch[A]): String
}
class Hipster extends Human{
 override def consume[A](drink: A)(implicit dispatcher: DrinkMultiDispatch[A]): String="hipster "+dispatcher.dispatch(drink)
}

trait DrinkMultiDispatch[A] {
  def dispatch(drink: A): String
}

Idea polega na dołożeniu "czegoś" co będzie decydowało o tym jak obsłużyć konkretny napój. Ponieważ mamy implicity to będzie pojawiało się znikąd i user nawet nie zauważy.

implicit val udawaneMultipleDispatch = new DrinkMultiDispatch[Drink]{
  def dispatch(drink: Drink) = drink match {
   case _:Vodka => "drinking vodka"
   case _:OrangeJuice => "drinking juice"
  }
 }

To jest takie oszukiwanie przez pattern matching. Próbowałem dopasować typ Dispatchera poprzez Generyk ale zawsze dostawałem "ambiguous implicit values" jak siedziały obok siebie [Vodka] i [OrangeJuice].

def workingMultipleDispatch(human:Human,drink:Drink)=human.consume(drink)
workingMultipleDispatch(hipster, vodka)   //> res3: String = hipster drinking vodka
workingMultipleDispatch(hipster, juice)   //> res4: String = hipster drinking juice
workingMultipleDispatch(antyhipster, vodka) //> res5: String = antyhipster drinking vodka
workingMultipleDispatch(antyhipster, juice) //> res6: String = antyhipster drinking juice

No i od bidy można powiedzieć, że to rozwiązanie uwalnia klasy "domenowe" od instanceof. Jeśli jest jakieś lepsze rozwiązanie to nie umiem go znaleźć. Zawsze trochę zabawy i edukacji.

A w ogóle skąd ten multi dispatch? Przeczytałem, że Clojure ma to z paczki ale podobno wolne strasznie.

*               *              *

poniedziałek, 14 kwietnia 2014

Warsztaty z Play i ciekawostki ze Scali

Zamiast zastanawiać się jak zacząć ten wpis aby oddać jak szczęśliwy jestem, że warsztaty udało się doprowadzić do skutku i spotkały się one z moim zdaniem dużym zainteresowaniem - użyję cudzej pracy i dam link do relacji Krzysztofa Telki z warsztatów

FoldLeft wszyscy szybko załapali

I z pisaniem testów też nie było problemów.

Tu jest reszta fotek:

Kolejne spotkania

Drugą edycję wprowadzenia będę chciał zrobić pomiędzy majówką a Geeconem. Na retro były prośby o więcej zadań - także zacząłem przygotowywać plan zajęć (a być może nawet i go skończyłem)

Jak ktoś ma sugestie niech śmiało zapodaje. Później po Geeconie a przed 33Degree (konferencje to dobry punkt odniesienia w czasie bo mogę zaplanować kiedy będę trzeźwy) zorganizuję jakoś drugi moduł "Obsługa formularzy, parsery requestów i kompozycja Akcji".

Layouty w Playu

W trakcie warsztatów pojawiło się pytanie, na które już umiem odpowiedzieć : "czy da się zrobić tak layouty aby dany tail nie wiedział w jakim szablonie jest używany".
O co chodzi : Play domyślnie wygeneruje nam szablon z rozwiązaniem "tail wywołuje szablon"

@main("Naglowek") {

 <h1>Moj tail</h1>


}
Jako że szablony to zwykłe funkcje to można też w drugą stronę : "wywołać taila z szablonu"
To źródło jest footer.scala.html
@import play.api.Play.current
<footer>
Revision @current.configuration.getString("application.revision")
</footer>
A to już wywołanie w szablonie
 <body>
        @content
        @footer()
 </body>
Proste?Proste.

Ciekawy przykład z życia

Na jednym ze spotkań firmowych dyskutowaliśmy sobie po co robić w deklaracji funkcji kilka par nawiasów i co dobrego z tego może wyjść. Wymyśliliśmy sobie funkcję "policjanta", która sprawdza czy dany user może wykonać jakąś tam operację :

def withProperAccess(user:User)(operation:User=>String)={
  if(user.securityLevel>2) operation(user) else "sorry ziom"
 } 
I teraz aby nawiązać do jakiejś formy dependency injection stworzymy funkcję, która z kontekstu wstrzyknie usera i wyprodukuje funkcje jednoelementową :
 def injectUser(function:(zagadka))={
   val contextUser=User("Roman",3)
   function(contextUser)
  } 
I tutaj pojawiła się zwiecha - jaki właściwie typ ma oryginalna funkcja dwuargumentowa, która jeszcze jako jeden z parametrów przyjmuje funkcje?(czyli co tam w ten nawias (zagadka) wstawić)

Okazuje się, że ów typ to : User=>(User=>String)=>String I teraz ktoś kto to widzi po raz pierwszy w życiu może zareagować niezwykłym zdumieniem wypływającym z silnego zderzenia jego obecnego pojęcia świata z napotkanym kształtem: Ewentualnie w twojej głowie może zabłysnąć niecodzienne zdziwienie wywołane kontrastującym konfliktem pomiędzy zmysłami a doświadczeniem:
A niepotrzebnie bo wyjaśnienie jest proste.

Co to jest?

Zaczynamy od strony lewej User=>(User=>String)=>String
Generalnie to co jest po lewej stronie strzałki to to co funkcja dostanie jako argument a to co po prawej (jak dziwne by nie było) to to co zwróci. Tutaj po stronie lewej jest User i to jest właśnie to magiczne wstrzykniecie usera z kontekstu - function(contextUser) i w wyniku dostajemy to po prawej.

Mamy teraz to coś : (User=>String)=>String i znowu to co jest po lewej to argument ale tym razem argument ma też strzałkę w sobie - bo toż to funkcyja jest ino. Argumentem jest funkcja, która przyjmuje Usera a zwraca String czyli np :

val funkcjaBiznesowa=(user:User)=>secretKeys.getOrElse(user.name, "nima")
Zaś wywołanie funkcji
(User=>String)=>String
Wygląda tak :
 val securityAccesWithUserInjected=injectUser(withProperAccessFun)
securityAccesWithUserInjected(funkcjaBiznesowa)

Mam nadzieję, że teraz jest trochę jaśniej.

*               *              *

niedziela, 6 kwietnia 2014

Scalar i stacktrace

Konferencja była bardzo fajna i można sobie poczytać wiele pochwał na twiterze : "#scalarconf". Dobra jak już posłodzone to teraz o jednej konkretnej rzeczy...

Zdrowy dystans do Scali

Bardzo chciałem posłuchać wykładu o wadach scali aby nie popaść w zbytnie uwielbienie technologii przechodząc metamorfozę w fundamentalistycznego fanatyka. Problem z konferencjami w sobotę jest taki, że dzień wcześniej jest piątek i wieczorem blokersi drą ryje na ośce co zaowocowało jedynie 4 godzinami snu. Ze względów bezpieczeństwa stymulanty zachowałem na podróż samochodem (obecnie jest to Thermo Shred co poza ogólnymi bonusami do statystyk dostarcza w jednej porcji 200mg kofeiny co wystarczająco wyostrza percepcję by bezpiecznie przebyć trasę Łódź-Wawka za kierownicą samochodu) przez co po obiedzie włączyła się standardowa zamuła.

Kiedy Tomek Nurkiewicz rozpoczynał wykład ja toczyłem bój o to aby przekierować jak najwięcej uwagi systemu nerwowego z trawienia na odbiór co zaowocowało drzemką przerywaną w postaci : jestem na Majorce-jestem na scalarze-jestem na Majorce-jestem na scalarze-i ostatnia faza to był taki mix Gry o tron z rosyjska inwazją na Krym ale zanim fabułą się rozwinęła obudził mnie jakiś koleś z komunikatem, że chciałby przejść do kibla.

Tomek mówił bardzo ciekawie co należy docenić podwójnie bo co innego jak angol mówi ciekawie po angielsku a co innego jak obcym językiem sprawnie posługuje się Polak. I nawet ten gostek co go zjadła trema na uderscore zrobił kawałek dobrej roboty i mam nadzieję, że nikt go nie hejtował na twiterach. Generalnie wkurwiają mnie ludzie, którzy nie mają jąder żeby samemu coś zrobić w życiu ale krytykują każdą wpadkę tych co ryzykują (tym bardziej, że sam tak kiedyś robiłem). Najlepsza scena to i tak jest wtedy jak jebany grubas z browarem i czipsami siedzi przed meczem i drze ryja "nooooo biegnij ku*waaa do tej piłki". Ale wracając do Scali.

Nie wiem czy ten temat był faktycznie poruszony czy to tylko widmo delirium poobiedniego ale generalnie temat nieczytelnych StackTraców już w kontekście scali widziałem razy kilka. A ostatnio ten argument pojawił się przy okazji lambady w Javie8. Następuje tu moim skromnym zdaniem pewne nieporozumienie łączące dwa niepowiązane fakty.

To, że stacktrace przy błędzie w funkcji anonimowej czy czymś takim może być z deczka nieczytelny nie wynika bezpośrednio z natury języka czy mechanizmów siedzących pod lambdą. Nieczytelność stacktrace wynika... z nieczytelnego stacktracu. Już tłumaczę o co chodzi.

Bo to zły stacktrace był...

Ten problem pojawia się nie tylko w Scali. Niedawno czytałem dyskusję o Coffescript i o tym, ze jak coś się popsuje to i tak trzeba debugować w javascript. Podobnie jest czasami z Hibernate jak używamy sobie klasy a tu nagle nam stacktrace o jakiejś tabelce gada. W tym miejscu zazwyczaj ludzie po raz 50000 cytują artykuł z przed 200 lat o "liking abstraction" ale już wystarczy tego suchara.

Tutaj koleżko opisuje jego bóle z stacktracem w scali i javie8 --> bóle - I zastanówmy się teraz przez chwilę dlaczego stacktrace Javy7 wydaje się być ok (a nie musi).

Jak mam np.

Object tekst=null;
tekst.toString();
To dostanę w Javie NullpointerException i ja wiem dokładnie gdzie on poleciał. ale tak wcale być nie musi.

Mogłoby być np. coś takiego

InvocationException in memory cell 561275
I dalej 150 dziwnych linijek zdradzających konstrukcje procesora. Stacktrace w Javie7 jest po prostu na tym samym poziomie abstrakcji co język a w javie8 i Scali jeszcze nie! Ale głowa do góry bo już gdzieniegdzie jest to poprawione. Np. Tak wygląda jeden z błędów w Playu.

Jak nie widać co jest na obrazku to trzeba w niego kliknąć. Generalnie nie ma tam błędu w stylu anon$12 czy coś w tym stylu ale jasno jest napisane, że nie ma obiektu danego typu a jedynie klasa i dla mnie osobiście ten poziom jasności jest wystarczający aby wiedzieć co się dzieje. Na scaladays 2013 było wystąpienie tego gościa od Springa co tam teraz w Ławie Typesafe zasiada i jasno dał im do zrozumienia, że zamiast jarzyc się tym, iż można całkę po granicy zbiorów nieokreślonych obliczyć w jednej linii - mają zrobić stacktracy czytelne dla zwykłych ludzi oraz stworzyć jakieś IDE, którego nie będą musieli się wstydzić - podejście zaczyna chyba działać.

Wracając jeszcze do percepcji stacktrejsów - generalnie kiedyś pisałem kod w stylu :

MOV A,10
Ta magiczna instrukcja umieszcza wartość 10 w akumulatorze procesora Z80 (tak tak pracownia była super zaawansowana). Ponieważ technikum orało mi mózg tym poziomem abstrakcji przez 5 lat to jak na studiach zobaczyłem : int a=a+1 to też się głowiłem jak to będzie można debugować jak nie wiadomo do którego akumulatora to trafia i z jakiej komórki pamięci. Tutaj jest fajna prezentacja o tym jak szybko technologie się zmieniają a ludzie zbyt wolno ---> Bret Victor - The Future of Programming

Z życia wzięte

Podobny mechanizm działa w korpo gdzie jedne levele abstrakcji komunikują się z innymi levelami abstrakcji. A piszę o tym tylko bo przypomniał mi się dowcip (uwaga dowcip):

- jak facet nie może dojść to wina faceta
- jak kobieta nie może dojść to wina faceta
I teraz w wersji korpo :
- jak biznes nie rozumie informatyków to wina informatyków
- jak informatycy nie rozumieją biznesu to wina informatyków
Ten obraz skutecznie podsycają różnego typu konsultanci aby nachapać więcej kapusty w kieszenie ale to już inna historia...

Podsumowanie

Konferencja bardzo fajna i za rok na pewno też pojadę chyba, że zaatakują ruscy - wtedy trzeba będzie znowu programować głowice jak w starym dobrym magnetofonie od C64 (chociaż nie, nie takie głowice)

niedziela, 30 marca 2014

Relacja z Play i nic poza tym

To jest jeden z tych postów pisanych na siłę aby coś przynajmniej raz na jakiś czas pojawiło się na blogu. Głównym tematem będzie recenzja z mojej prezentacji na JUGu, którą to nie ja napisałem----------> Nie moja recenzja mojej prezentacji

Ponadto w najbliższą środę robię prezentację o Play dla studentów. A, że jeszcze nigdy nie robiłem prezentacji dla studentów to będzie to jakieś nowe doświadczenie : Nie mój link do mojej prezentacji.

Powklejał bym jeszcze trochę linków do nie swoich materiałów ale nawet na to nie ma czasu ostatnio.

niedziela, 2 marca 2014

Play - jak kompozycja parserów ułatwia życie

Każde rozwiązanie potrzebuje problemu

Aby pokazać, że problem nie jest jakoś oderwany od rzeczywistości zerknijmy na tego oto linka : TomTomowe api do geocodowania adresów. A w zasadzie to zerknijmy tam na sam dół do sekcji "Batch queries". To jest opcja serwisu, która przyjmuje na chwilę obecną zapytania zawierające do 100 adresów i zwraca nam różne ciekawe informacje.

Tak dla ćwiczeń spróbujmy zaimplementować podobny serwis, który po prostu zwróci listę miast przesłanych w zapytaniach zawartych w pliku xml. I żeby było optymalnie to plik będzie spakowany zipem.

Co jest tak naprawdę "Logiką Biznesową"

Jest nią ta krótka funkcja :

def findCities(xml:Elem)=xml \\ "locations" \\ "location" \\ "L" map(_.text) reduce((a,b)=>s"$a,$b")
I teraz kilka uwag co do tego kodu :
  • Tak ta jedna linijka parsuje xml, wydobywa z niego wszystkie miasta i łączy je w jeden text oddzielając przecinkami.
  • Tak, po pewnym czasie obcowania z językiem Scala do tej linijki można dołączyć przymiotnik czytelna
  • I Nie, to nie jest część terapii leczenia kompleksów programistycznych poprzez obcowanie z jednolinijkowcami.

Problem jest taki, że zanim będziemy w stanie wykonać tę linijkę to musimy jakoś przyjąć zzipowany plik, rozpakować go, zrobić z niego typ XML i dopiero przekazać do logiki. Tutaj do akcji wkraczają parsery...

Parsery

Ponieważ krążą plotki, że pisanie parsera w playu od zera może wysłać programistę Javy na terapię wiec pokażemy jak łatwo komponować gotowe już parsery. Na początek mamy gotowca, który zapisuje przesłany plik tam gdzie chcemy. Tutaj na szybko przykład na pałę skopiowany z dokumentacji :

def save = Action(parse.file(to = new File("/tmp/upload"))){request=>
  Ok("Saved the request content to " + request.body)
}
Wygląda łatwo i używa się tego też bardzo łatwo. Teraz chcemy go użyć do stworzenia parsera, który nam jeszcze ten plik rozpakuje :
val uncompressBodyToTemp = parse.using { request =>
    parse.file(createTempFile) map(unzip)
}
I to w zasadzie tyle. No prawie bo createTempFile i unzip trzeba napisać samemu ale nie jest to nic trudnego.

Interakcja z Javą

A Scala z Javą współpracuje bardzo dobrze. Poniżej np. widzimy wygodne wykorzystanie klasy Files z Javy 7.

 def createTempFile = Files.createTempFile("geo_", "_file.zip").toFile()
A teraz unzip. Tutaj taka ciekawostka - jak się pogugluje "scala unzip file" to się ch.. znajdzie. Za to do Javy bibliotek trochę jest. I bardzo łatwo te biblioteki w Scali zastosować. Tutaj użyjemy zip4j, która jest po prostu zajebista.

Na chwilę obecną jestem na etapie gdzie jeszcze nie rozumiem czym dokładnie SBT różni się od mavene (SBT to system budowania dla Scali) ale generalnie zależności z repo mavenowego dodaje się tam dosyć prosto

libraryDependencies ++= Seq(
  jdbc,
  anorm,
  cache,
  "net.lingala.zip4j" % "zip4j" % "1.3.2"   <---- o tutaj to wkleiłem
)  

Dobra to teraz funkcja "unzip" i kilka ciekawostek.
  def unzip=(inputfile: File) => {
   import collection.JavaConverters._ //1
   val zipFile=new ZipFile(inputfile) //2
   val fileName=zipFile.getFileHeaders().asScala.head.asInstanceOf[FileHeader].getFileName //3
   zipFile.extractFile(fileName,"/tmp") //4
   new File(s"/tmp/$fileName")
  }
  1. Tutaj importujemy konwertery ubogich kolekcji w Javie do bogatych kolekcji w scali.
  2. Ta linia w Javie zmusiła by nas do obsługi checked exception
  3. Tutaj mamy przykład konwersji listy Javowej do Scalowej. Ponieważ ta lista sama w sobie jest zjebana gdyż nie ma generyków i przechowuje czyste obiekty toteż trzeba dać rzutowanie. w javie ta linia wyglądałaby dla porównania następująco : (czytelność do oceny czytelnika):
    • String fileName = ((FileHeader)zipFile.getFileHeaders().get(0)).getFileName();

To jak wygląda w końcu ta cała akcja

Ano wygląda tak :

  def apiUpload=Action(uncompressBodyToTemp){request=>
      val xmlFile=scala.xml.XML.loadFile(request.body)
      Ok(findCities(xmlFile))
  }
Czy można jeszcze lepiej. Ano można - po cholerę akcja ma się zajmować pakowaniem pliku do xmla.
def convertToXml(file:java.io.File)=scala.xml.XML.loadFile(file)
val uncompressBodyToXml= uncompressBodyToTemp map convertToXml
def apiUpload=Action(uncompressBodyToXml){request=>
      Ok(findCities(request.body))
 }
Może miałem w życiu pecha ale nie widziałem żeby w czystej obiektówce można było uzyskać taki poziom kompozycji.

Hola hola a co z formularzami?

Poprzedni przykład był dobry dla usług, które oczekują, że plik będzie jedyną rzeczą jaka do nich przyjdzie w rekłeście. W przypadku formularzy nie można tego tak łatwo zrobić bo tam są multiparty i inne takie tam. Ale wiadmo - w playu jest gotowiec i na to : parse.multipartFormData

Kod może być na początku leciutko bardziej skomplikowany ale zaraz to sobie wytłumaczymy :

val uncompressFormToTemp = parse.using { request =>
    val tmpFile = createTempFile
    parse.multipartFormData.map(multipartFormData=>multipartFormData.file("query")
      .map { filePart =>
        filePart.ref moveTo (tmpFile, true)
        tmpFile
      }.map(unzip)
    )
  }
To byłą wersja wydłużona - a teraz wersja skrócona :
val uncompressFormToTemp = parse.using { request =>
    val tmpFile = createTempFile
    parse.multipartFormData.map(_.file("query")
      .map { filePart =>
        filePart.ref moveTo (tmpFile, true)
        tmpFile
      } map unzip
    )
  }
No może nie jest za bardzo skrócona ale to był tylko pretekst do małej dygresji o tych podkreślnikach co się w Scali tu i tam pojawiają :

Dygresja

Od czasu do czasu, ktoś obcujący ze Scalą może natrafić na "Emotikon Driven Development" np.

List(1,2,3) reduce(_+_)  //sumuje wszystkie elementy listy
List(1,2,3) reduce(_*_)  //iloczyn wszystkich elementów listy
Można to także zapisać w sposób :
List(1,2,3) reduce((a,b)=>a+b)
List(1,2,3) reduce((a,b)=>a*b)
I teraz co następuje - gdy tak już masę razy - raz za razem zapisuje się kompletne deklaracje funkcji to pomału staje się to męczące. W pewnym momencie w głowie pojawia się trudna do wyartykułowania potrzeba uproszczenia tego zapisu. I nagle wokół literek na ekranie zaczyna się ujawniać niezwykła lekko zielonkawa świetlista otoczka - zaczynają się one niejako ruszać próbując ci coś przekazać. Ekran staje się giętki i plastyczny. Nie jesteś w stanie stwierdzić gdzie się kończy a gdzie zaczyna.

Nagle następuje ocknięcie. Rozglądasz się po pokoju i zauważasz, że wszystko wygląda tak samo a jednak coś się zmieniło. Twoja postać uzyskuje nową zdolność - czytanie kodu z podkreślnikami

Tego nie da się logicznie argumentować - to trzeba przeżyć

Koniec dygresji

Akcja formularza

Niby tak samo ale trochę inaczej. Generalnie parser zamiast pliku zwraca nam Option[File] ponieważ coś tam w międzyczasie mogło pójść nie tak (ot nie dostaliśmy żadnego pliku w multiparcie)

def lift[A,B](f:A=>B):Option[A]=>Option[B]=_ map f
val uncompressFormToXml =uncompressFormToTemp map lift(convertToXml)
I tutaj z okazji, że będziemy operować na Option trzeba leciutko udekorować naszą funkcję konwersji do xml. Widzimy, że znowu bardzo ładnie się wszystko komponuje. Oczywiście funkcję lift można by nazwać ejWeNotMiToNAOpszionaPrzmien ale używając nomenklatury z fachowej literatury będę wyglądać na mądrzejszego niż w rzeczywistości jestem.

I w końcu akcja:

def upload = Action(uncompressFormToXml) { request =>
    request.body.map{file=>
      Ok(findCities(file))
    }.getOrElse(BadRequest)
  }
I proszę "ło matko" obsługujemy zły request beż żadnych ifów. Jak coś się wyjebało to będzie BadRequest. i tyle. Sądzę, że cała wymieniona do tej pory logika zmieści się w kilkudziesięciu linijkach co odpowiada długości relatywnie dobrze utrzymanej metody Javowej. A czytelność - jak się nauczy Scali to się będzie umiało tym bawić.

Re użycie

Mamy nasze parserki i mogą one zacząć na siebie pracować. Stworzymy teraz funkcjonalność pro-edukacyjną, która ma obronić młodzież przed zbereźnymi tekstami. Np takimi jak w Panu Tadeuszu :

Klucznikiem siebie tytułował,
Iż ten urząd na zamku przed laty piastował.
I dotąd nosił wielki pęk kluczów za pasem,
Uwiązany na taśmie ze srebrnym kutasem
logika :
def cenzuruj(text:String)=text.replaceAll("kutas", "k___s")
akcja:
def cenzura=Action(uncompressFormToTemp){request=>
    import scala.io.Source._
    request.body.map{file=>
     val lines=fromFile(file).getLines()
     Ok(lines.map(cenzuruj).mkString("\n")) 
    } getOrElse(BadRequest)
  }
I młodzież uratowana.

*      *      *

niedziela, 16 lutego 2014

Jakie diagramy lubimy najbardziej

Masz przed sobą diagram klas albo diagram organizacji albo diagram czegokolwiek. Bardzo ale to bardzo chcesz wierzyć, że swoim specjalistycznym okiem potrafisz zmierzyć wartość merytoryczną tego na co patrzysz i wykonać coś co masz nazwać logicznym osądem.

Co jeśli jednak osąd jest emocjonalny i bazuje nie na "zawartości" a na estetyce diagramu? Wtedy na pewno załamie się nasz wizerunek jako profesjonalistów dlatego łatwiej pewne rzeczy zwyczajnie wyprzeć ale jeśli ktoś jest ciekaw to poniższy tekst powstał na podstawie książki :

Jakie diagramy są najprzyjemniejsze

My jako ludzie generalnie lubimy drzewa rosnące na sawannie w obecności wody. To zostało udowodnione w eksperymencie naukowym także brzmi poważnie (--> development of visual preference for natural environments). Ale o tym za chwilę. Na początek coś prostego :

Który diagram na pierwszy rzut oka "wydaje się lepszy"? Jeśli wszystko w głowie jest poukładane tak jak być powinno to diagram drugi i znowu są pewne badania (-> Richard Latto ) według których ludzie lepiej sobie radzą z przetwarzaniem linii pionowych i poziomych aniżeli ukośnych czy krzywych jeszcze inaczej.

Czyli pomimo, że powyższe dwa rysunki nie przekazują żadnej treści to jednak (przynajmniej według pewnych badań) obserwator będzie preferował jeden ponad drugi. I jeszcze jedno :

Znów - chociaż "treściowo" diagram jest identyczny z poprzednim to według o tych tutaj : Reber R, Schwarz N, Winkielman P. (Processing fluency and aesthetic pleasure: is beauty in the perceiver's processing experience?) nasz gatunek oceni ten ciemniejszy jako "mniej przyjemny", "bardziej złożony" itd. (Zresztą tu nie potrzebne są badania.Raczej każdy musi się przyjrzeć dłużej zaciemnionemu rysunkowi chyba, że jest batmanem - wtedy :˙ɯǝzɹǝdoʇǝıu śǝʇsǝɾ ǝż ʎzɔɐuz oʇ ćɐʇʎzɔǝzɹd oʇ zsǝżoɯ ıןǝżǝſ)

I co z tymi drzewami?

A jakie konsekwencja w kwestii rysowania/projektowania modelu obiektowego czy czegoś takiego ma fakt, że kiedyś skakaliśmy sobie po gałęziach od banana do banana? Według niejakiego J.C. Sprotta (Szprota - > Can a Monkey with a Computer Create Art?) nasza ewolucja wyrzeźbiła pewne preferencje.

Co to za preferencje? Mamy pustą kartkę i miarę D. Na początku D ma wartość 1. Zaczynamy rysować linie i gdy te linie przykryją kartkę całkowicie to D=2. I jak wieść niesie ludzie oceniają rysunek jako "przyjemny" w zakresie D od 1.1 do 1.5 a najlepiej gdy D=1.3 . Teoria jest taka, że przy D=1 teren jest zbyt odsłonięty a przy D>1.5 Czort wie co tam się może czaić za zaroślami (czyli np. taka zarośnięta dzungla).

W związku z tym powyższy diagram powinien raczej wywoływać negatywne odczucia (bez względu na treść) i jak do tego dorzucimy odpowiednie racjonalizacje to wyrobionym profesjonalnym tonem stwierdzimy, że ten "projekt jest zły" (chociaż równie dobrze moglibyśmy po prostu zjeść sobie banana - dużo potasu ale uwaga na węgle!) .