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)