środa, 31 sierpnia 2016

Program jako funkcja całkowita - czyli kiedy naprawdę nie ma błędów

"Litr czystej poproszę" - to zdanie wypowiedziane w całodobowym sklepie branżowym niesie ze sobą wystarczająca ilość informacji aby było wiadomo "o co chodzi". A wiadomo o co chodzi gdyż kontekst już definiuje pewne pojęcia jak to, że chodzi o napój w tym sklepie, że ma się to odbyć teraz i że popłynie hajs. Te rzeczy są oczywiste.

Wynik 2+2=4 także jest oczywisty. Jeśli zapytamy o rezultat porównania 2 + 2 == 4 to czytelnik o ponadprzeciętnej podejrzliwości może szukać podstępu w definicji operatora + (tak, w niektórych językach można go zdefiniować jak się chce) albo widzi spisek w tym jak operator == porównuje wartości, a może jest tu jakiś "Boxing" a może porównuje referencje? Raczej mało komu przyjdzie do głowy, że może mechanika działa prawidłowo ale 2+2 to wcale nie 4? eeee ... że co? Brzmi to z pozoru absurdalnie ale jednak są ludzie, którym oczywistość nie wystarcza i wyprowadzili wcale niebanalny dowód że 2+2 to faktycznie 4

Teraz inna sytuacja - słyszysz zdanie : "W moim programie jest Błąd" a jeśli ma być światowo to "W moim programie jest Bug" i zazwyczaj rzeczą, na której koncentruje się sytuacja jest ten Bug. Raczej nie usłyszysz w odpowiedzi "W twoim programie jest Bug, a jak zdefiniowałeś pojęcie programu?" albo "zdefiniuj warunki wymagane do stwierdzenia jest Błąd" czy wyjątek to błąd , czy sam fakt, że może polecieć wyjątek ale nie poleciał to błąd? Czy jeśli nie leci wyjątek a jest zły rezultat to jest to błąd? No i w końcu czy jeśli wszystko działa to w programie jest błąd czy go nie ma?

Po tym dowodzie na dwie strony, że 2+2=4 ostatnie zdanie nie powinno być takie oczywiste - Czy jeśli program działa i zwraca poprawny wynik to jest w nim błąd?.Zagłębmy się w ten ciekawy problem i zdefiniujmy "Program" i "Błąd".

Korzenie

Powyższy obrazek jest skopiowany ze strony na wikipedii : Human Computer. Bo oto okazuje się, że komputery istniały jeszcze zanim powstały ...komputery.

The term "computer", in use from the early 17th century (the first known written reference dates from 1613), meant "one who computes": a person performing mathematical calculations, before electronic computers became commercially available.

Ha! Czyli nie dość, że trzeba zdefiniować co to jest program to jeszcze nie jest jasne co to jets ten "komputer". I teraz sformułowanie "Babcia kupiła mi na komunie komputer" może nawet zakończyć się sprawą w sądzie na skutek uprawiania niezgodnego z prawem niewolnictwa!

Ciekawy jest fragment o tym co oni robili "a person performing mathematical calculations". Pewnie nie jeden profesorek rzuci teraz we mnie cyrklem ale teoretycznie coś takiego :


fun czyProstokątny(a:Integer,b:Integer,c:Integer):Boolean = pow(a,2)+pow(b,2) == pow(c,2)

to też jest jakieś tam "mathematical calculations" i przy założeniu, że "a" i "b" to przyprostokątne a "c" to zawsze przeciwprostokątna to ta funkcja sprawdza czy trójkąt jest prostokątny. Ale czy sprawdza? Tego nie wiadomo. Bo co robi funkcja "pow"? Być może wydaje z głośnika na płycie głównej dźwięki "POWPOW" ?

Przydałoby się tutaj jakieś twierdzenie w stylu Dla każdego x należącego do zbioru liczb całkowitych pow(x,2)=x * x . Oczywiście można tak jechać dalej ze znaczkiem "*" ale tutaj założymy, że to mnożenie, które działa "dobrze" bez wnikania w definicję "dobrze" bo to jest tylko wstep do ciekawszych rzeczy.

Czy coś może pójść nie tak?

Wydaje się, że pierwszym problemem może być przekroczenie zakresu Integera. Jaki jest zakres Integera ? "Ja wiem!Ja wiem! W javadocu jest , że 2 31 - 1"

I to jest właśnie takie myślenie językiem programowania. Czasem niestety jednocześnie pierwszym i jedynym jaki dana osoba napotka w życiu. No bo zobacz - taki Haskell wydaje się mieć trochę na zakresy wyjebane :

Dowód na czytanie z pliku?

Ciekawie zaczyna się robić kiedy zaczniemy drążyć "skąd dokładnie brane jest a,b i c"? Niechże to będzie plik CSV z jedną linią by było prościej

3,4,5
I teraz np mamy dwie dodatkowe funkcje
fun czytaj(plik:File):String
fun parsuj(linia:String):(a:Integer,b:Integer,c:Integer)
Ba i nawet teraz można zrobić kompozycję bezpośrednio z pliku w wynik sprawdzania czy trójkąt jest prostokątny
czytaj andThen parsuj andThen czyProstokątny

- Łooołooołooołoo łooooo chwila chwila chwillllla a co jak się wyjebie?"
- ale co?
- no to czytanie z pliku?
- ale ... ale jak?
- no jak pliku nie będzie?
- no.. no.. to może załóżmy, że zawsze jest...

Wydaje mi się, że powyższy problem jest przedstawiony bardziej fachowo (i bez słowa "wyjebie" które może zgorszyć niejednego przedstawiciela zblazowanej klasy średniej) w poniższym abstrakcie.

Otóż takie teoretyczne rozważania rypią się gdy mamy do czynienia z czymś co dosyć trudno przedstawić "matematycznie" jak na przykład czytanie z bazy czy przesyłanie danych po sieci - no bo jak , no jak wygląda teoretycznie poprawna funkcja "zaciągnijLegalnieFilmZTorrenta". No i dopiero wyjście z tej sytuacji znalazł Eugenio Moggi a smaczku dodaje, że abstrakt pochodzi z jego pracy zatytułowanej Notions of computation and monads (moooooooooooooooonads!!!)

Definicja BUGa jako funkcji

Tak dla przypomnienia - funkcja całkowita to na przykład dodawanie bo co byśmy tam nie podali to i tak zadziała. Funkcja częściowa to dzielenie bo dla argumentu gdzie dzielimy przez zero nie jest ona określona. Jeśli zastosujemy dzielenie z założeniem, ze zadziała dla każdej liczby - wtedy mamy błąd.

funkcja FormularzRejestracyjny=>User jest określona dla każdego poprawnie wypełnionego formularza ale nieokreślona dla Formularza, który w polu wiek ma wartość "DaaaajKamienia"

funkcja Plik=>String jest określona jedynie dla zbioru poprawnie zdefiniowanych i plików.

funkcja IdZamowienia=>Zamowienie jest określona jedynie dla istniejących zamówień.

funkcja OdpytanieZewnętrznegoAPI => Wynik też jest częściowa bo określona tylko dla sytuacji kiedy otrzymamy jakiś wynik.

Nie wiem czy ktoś z was podchodizł do tego od tej strony - ja kiedyś nie. Zazwyczaj po prostu chodziło o obsługę wyjątków. Tymczasem na obsługę wyjątków można trochę tak patrzeć jak na taki zewnętrzny hak, który umożliwia nam używanie funkcji częściowych tak jakby one były całkowite. Czyli poza standardowym znaczeniem BUGa kiedy to funkcja zwraca po prostu zły wynik (f(a,b)=a+b+1 - funkcja czysta ale zjebana) dochodzi dodatkowy rodzaj Błędu.

Błąd - sytuacja w której, funkcja częściowa jest używana tak jakby była całkowita. Np takie coś

 sprawdzLinie:String=>Boolean = parsujLinie and Then czyProstokątny
można zdefiniować jako błąd gdyż funkcja parsujLinie w kontekście naszego przykładu jest zdefiniowana jedynie dla takiej formy gdzie w linii jesta,b,c i to nawet może przejść wszystkie testy i chodzić na produkcji 5 lat. Cały czas kwalifikuje się to jako błąd.

Jak sprawdzić czy funkcja jest całkowita? - scalacheck.

Wizualizacja funkcji częściowej. Czerwona Kulka może symbolizować zapytanie do bazy danych, które zakończyło się niepowodzeniem.

Kompozycje i efekty

Cały problem z wyjątkami polega na tym, że przeszkadzają one w kompozycji a kompozycja to "Reusage (Rejusedż)" czyli święta gral programowania. No bo jak mam funkcję String=>(Int,Int,Int), która mówi mi, że zwraca trzy elementowego Tupla (z polskeigo krotkę) a tak naprawde zwraca tego Tupla albo ch.. wie jaki wyjątek to ja nie moge sobie prosto tej funkcji skomponować z (Int,Int,Int) => Boolean bo na wejściu wcale nie muszą pojawić się trzy inty!!!

No dobra ponarzekalimy ale gdzie jest SOLUSZYN? Zamiast udawać, że funkcja częściowa to funkcja całkowita i try-catchem łapać pokemony może da rade zamienić jakoś te funkcję częściową w całkowitą? I to zaproponował właśnie Eugenio Moggi - chyba to zaproponował bo połowy jego pracy nie rozumiem ale poznaje rysunki, podobne do tych które są na stronie wiki o monadach (te takie ze strzałkami).

Klasycznie jest coś takiego :

val divide:(Int,Int)=>Int = (a,b) => a/b

try{
    divide(4,0)
}catch{
    case e:ArithmeticException => println("Kalwi & Remi - EXPLOSION")
}
I naprawdę ważne jest aby zrozumieć, że tutaj to try, jest takim zewnętrznym klejem który utrudnia kompozycję.

Dalej mamy dedykowany typ dla funkcji częściowej:

val partialDivide : PartialFunction[(Int,Int),Int] = {
    case (a,b) if b!=0 => a/b
}

println(partialDivide.isDefinedAt(4,0))  //false
Tutaj już plus jest taki, że nie udajemy, iż każdy argument ma jakiś wynik.

I teraz następuje zamiana funkcji częściowej na całkowitą

val totalDivide: ((Int, Int)) => Option[Int] =partialDivide.lift

println(totalDivide(4,0))  //None
println(totalDivide(4,2))  //Some(2)

"totalDivide" jest funkcją całkowitą i dla każdego argumentu zwraca ona rezultat. Tam gdzie dzielenie ma sens będzie to Some tam gdzie nie ma będzie None ale będzie! Może powstać pytanie ale co z tego jak w wyniku dostajemy jakiś "twór"? Ów twór jest tzw. Typem wyższego rzędu (po javowemu "klasa z generykiem") i obsługuje się go przy pomocy funkcji wyższego rzędu oraz pattern matchingu. Drugiego mechanizmu w Javie nie ma a pierwszy jest od niedawna dlatego w półswiatku javowym owe podejście może wydawać się bardzo egzotyczne.

Co z kompozycją? Jest cała teoria opisująca jak to się komponuje i ze zwykłymi funkcjami jak i z innymi tworami. Jedno popularne nazewnictwo używa na twory słów Funktor, Monada itd ale też spotkałem się z podejściem do tego jako do Efektu i np. Option to Efekt braku czegoś a Future Efekt czasu w obliczeniach.

Owy efekt spełnia pewne prawa i aby się przekonać, że na pewno je spełnia możemy użyć scalacheck. Ba! ScalaZ ma nawet gotowy zestaw testów Scalachecka by sprawdzić czy aby na pewno wiemy z czym mamy do czynienia : Functor Laws

No i jak nasz efekt spełnia prawa Funktora to możemy śmiało pisać z założeniem, Option(x).map(f).map(g) to to samo co Option(x).map(f andThen g)

Tyle w temacie

Celem tego wpisu nie jest brnięcie dalej ale własnie zatrzymanie się w tym miejscu. Temat jest jak rzeka a za węgłem czekają Funktory,Monady, Monoidy, Foldable,Kleisli, Transformatory Monad czy inne potężne narzędzia kompozycji efektów. Zazwyczaj na co dzień nie mamy czasu/ochoty zatrzymać się na chwilę i poddać kontemplacji nad tym co my właściwie robimy. Jednak należy pamiętać, że nasze obecne wnioski budowane są na dogmatycznych założeniach - jeśli czasem im się bliżej nie przyjrzymy to wiele ciekawych dróg i perspektyw może być dla nas na zawsze zamkniętych :(

Anegdota o .Necie

Zazwyczaj na początku nauki instaluje się jakiś kompilator , pisze hello world i dalej jakieś tam proste przykłady. Ponieważ bez ogólnych podstaw teoretycznych mechanizmy konkretnego języka staną się jedynymi ilustracjami tychże mechanizmów to istnieje niebezpieczeństwo, że ludzie zaczną utożsamiać implementacje z koncepcjami. W javie to będzie na przykład "polimorfizm - aha czyli dziedziczenie klas" albo powtarzanie bez głębszego zastanowienia, że wszystkie pola w klasie (bo przecież nie może być poza klasą nie?) muszą być private bo..bo.. bo enkapsulacja i ch*j.

No i raz było w Łodzi spotkanie grupy devs@lodz, która wykształciła się z grupy dot netowej. No i jest to spotkanie i gostek gada o MVC ale tak gada jakby używał cały czas słowa samochód ale chodziło mu o jedynie o Poloneza. Od słowa do słowa zrozumiałem, że Microsoft im tam potworzył po jednej implementacji wszystkiego i MVC to po prostu nazwa frameworka i też w moim odczuciu, trochę zdziwili się, że jak w Javie chcesz mieć MVC to myślisz o koncepcji i wybierasz sobie dopiero jedno z narzędzi, które to realizuje.

Innym analogicznym przykładem rozmowa o systemie operacyjnym gdy znasz tylko windowsa - "dobra znajomość systemu operacyjnego wymaga płynnej obsługi menu start" - czy coś w tym stylu.

Studiowanie to gorący temat w IT zwłaszcza, że w jakimś tam rankingu ostatnio dwie polskie uczelnie były w 5 setce a żadna Łódzka się nawet nie załapała ale polecam pogrzebać na edx czy courserze i można znaleźć naprawdę ciekawe kursy online dostarczane przez uczelnie z samej czołówki. Do tego wiele z tych uczelni ma swoje własne portale jak http://ocw.mit.edu/index.htm



czwartek, 18 sierpnia 2016

Historia Kulki Animowanej

Jest to wpis o historii pewnej Kulki. Nie jest to jednak zwykła kulka bo to historia kulki animowanej i to animowanej javascriptem. I nawet ona spada według jakichś tam praw fizyki i nawet za sobą zostawia ślad (rysowany też pośrednio javascriptem). Jest to kulka w javascripcie ale bez pisania w javascripcie.

Podążając za poprzednim dającym (na opewno mnie) nadzieję na łeba wpisem "Scala.js taka piękna" cały javascript jest wygenerowany przy pomocy Scala.js. I (jak dla mnie) kod jest fajnie. Bo są typy. I Jest fajnie.

Kino 8D

Czcionka na gifach jest mała dlatego trzeba w nie kliknąć albo skrolem powiększyć. Na pierwszym przeźroczu widać, że wielkość kulki można konfigurować i ona tak rośnie i maleje wtedy od razu. Do tego na bieżąco liczona jest masa tejże kulki z założeniem, że gęstość to średnia gęstość meteorytu czyli 3.4 g/cm^3

Jest tam także validacja pola gdzie można wprowadzić promień kulki. A, że rrrr to nie jest najlepsza wartość dla promienia dlatego warto ją sprawdzić. Tutaj dla podkreślenia możliwości Scali.js do validacji została użyta Monada. Jest to tylko Try ale zawsze Monoada®© Zawsze można się (nie)znajomością teorii kategorii popisać przed kolegami od front-endu.

No i w końcu "leć Adam leć". Zwróćcie uwagę, że wraz z czasem odległość pomiędzy czerwonymi kropkami się zwiększa a to dlatego, że działa przyspieszenie i składowa y prędkość się także powiększa z czasem co obrazuje wydłużająca się pionowa strzałka symbolizująca pionowa składową wektora prędkości.Dokładnie tak. W symulacji użyte jest przyśpieszenie księżyca 1.6 m/s aby kulka za szybko nie spadła i aby spektakl się za szybko nie zakończył.

"Z życia programisty front-endu"

Cały eksperyment miał za zadanie sprawdzenie "Jak się w tym pisze". Pierwsza ważna rzecz na jaką należy zwrócić uwagę przy zasiadaniu do nowej technologii to czy na StackOverflow są już odpowiedzi do wszystkich zadań

Generalnie te strzałki w symulacji moje nie są jeśli za "moje" uznać napisane własnoręcznie. Po wpisaniu w google arrow canvas javascript mamy ten link :

I na przykład tak to wygląda w javascript (kim czym - jawaskrypcie?)

//Javascript
function canvas_arrow(context, fromx, fromy, tox, toy){
    var headlen = 10;   // length of head in pixels
    var angle = Math.atan2(toy-fromy,tox-fromx);
    context.moveTo(fromx, fromy);
    context.lineTo(tox, toy);
    context.lineTo(tox-headlen*Math.cos(angle-Math.PI/6),
      toy-headlen*Math.sin(angle-Math.PI/6));
    context.moveTo(tox, toy);
    context.lineTo(tox-headlen*Math.cos(angle+Math.PI/6),
     toy-headlen*Math.sin(angle+Math.PI/6));
}

i naprawdę niewiele trzeba by ten kod zadziałał w Scala.js

// Scala.js
def canvas_arrow(fromx:Int, fromy:Int, tox:Int, toy:Int){
      val headlen = 10   // length of head in pixels
      val angle = Math.atan2(toy-fromy,tox-fromx)
      ctx.moveTo(fromx, fromy)
      ctx.lineTo(tox, toy)
      ctx.lineTo(tox-headlen*Math.cos(angle-Math.PI/6),
       toy-headlen*Math.sin(angle-Math.PI/6))
      ctx.moveTo(tox, toy)
      ctx.lineTo(tox-headlen*Math.cos(angle+Math.PI/6),
        toy-headlen*Math.sin(angle+Math.PI/6))
    }

Jestem w DOMu

Generalnie jedyna niedogodność do jakiej można się doczepić, to że używając "zwykłego DOM" pobieramy generyczne elementy HTML, które trzeba mapowac na konkretne Typy

//Scala.js
//jest troche więcej rzutowania przy pobieraniu elementów
val canvas=document.getElementById("canvas").asInstanceOf[Canvas]
val context=canvas.getContext("2d").asInstanceOf[dom.CanvasRenderingContext2D]

//ale dalej jest już w zasadzie to samo
context.fillStyle="#FF0000"
context.fillRect(0,0,150,75)

Ktoś przyzwyczajony do Javascript może narzekać, że to jakiś dodatkowy wysiłek ale to jest inwestycja, która zwraca się w dalszej części pracy z tymi elementami kiedy zabezpieczają nas te właśnie typy typy. (No chyba, że ktoś źle zrzutuje wtedy boli)

//Javascript 

var canvas = document.getElementById("myCanvas");
var ctx = canvas.getContext("2d");
ctx.fillStyle = "#FF0000";
ctx.fillRect(0,0,150,75);

Generalnie można pisać w Scala.js korzystając z dokumentacji do javascript. Ot choćby mechanizmy z tej stronki działają w Scali jak najbardziej : http://www.w3schools.com/html/html5_canvas.asp

Troski o jednostki

Na razie Scala.js goni javascript ale czy może zaoferować coś czego tam nie ma? (ewentualnie jest ale ja nie wiem, że jest). Otóż jak już ogarniemy przyciski i inputy to jest pewien problem poniżej bo trzeba nam np. wyliczyć masę ze wzoru m=ρ * (4/3) * π * r3 gdzie ρ - to gęstość. I teraz weź to wszystko licz przy pomocy double gdy gęstość to g/cm3 a promień jest w metrach. Czy też licz drogę w kilometrach gdy prędkość metr/sekunda a czas symulacji jest w milisekundach by się to jakoś odświeżało.

I w scali np moge stworzyć sobie odpowiednie typy, które będą kontrolowały jednostki. To poniżej nie jest jakimś szczytem modelowania domenowego bo Centy,Mili,Kilo trzeba powtarzać dla każdej jednostki ale generalnie chodzi o eksperyment jak to się sprawdzi w Scala.js (No i dla porównania zmienię formatowanie kodu bo nie mogę się zdecydować czy lepsze jest jaśniejsze czy ciemniejsze.)

object Distance{

  case class Centimeter(v:Double) extends AnyVal {
    def meters = Meter(v/100)
  }

  case class Meter(v:Double) extends AnyVal{
    def centimeters=Centimeter(v*100.0)
  }
  case class KiloMeter(v:Double) extends AnyVal{
    def meters=Meter(v*1000.0)
  }

  implicit def meterToDouble(m:Meter):Double=m.v
}

Pierwsza rzecz to kontrola przy liczeniu objętości kuli. Chcemy utrzymać czyste jednostki bez żadnego przedrostka.

case class Volume(v:Double) extends AnyVal

def sphereVolume(r:Meter):Volume=Volume(4.0/3.0 * PI * pow(r,3.0))

No i drogi czytelniku/czytelniczko mam nadzieję, że zauważyłeś/aś tam ciekawą rzecz, że podajemy bezpośrednio promień który przecież jest typu "Meter" do Math.pow. A to dlatego, że zaimportowaliśmy konwersję z modułu Distance

implicit def meterToDouble(m:Meter):Double=m.v

No i jeszcze jedna rzecz - AnyVal. W Scali kompilator rozpakuje prymitywa i w runtime nie ma żadnego narzutu a w trakcie kompilacji typy zabezpieczają. (to tak jak wypić wódkę i mieć wódkę czy coś takiego)

No i z tego kodu już po kompilacji do js wynika, że to też działa bo bez AnyVal jest wersja gdzie wartość z promienia trzeba dopiero wydobyć (czyli to tak jak mieć ciastko i zjeść ciastko albo tak jak nie płacić abonamentu a i tak oglądać telewizję).

//Z valueclass
$c_Lcom_wlodar_physics_Geometry$.prototype.sphereVolume__D__D = (function(r) {
  return (4.1887902047863905 * $uD($g.Math.pow(r, 3.0)))
});

//bez value class
$c_Lcom_wlodar_physics_Geometry$.prototype.sphereVolume__Lcom_wlodar_physics_Distance$Meter__D = (function(r) {
  var a = r.v$1;
  return (4.1887902047863905 * $uD($g.Math.pow(a, 3.0)))
});

Rzeczy nowe

Przy okazji takiego programu pojawia się kilka ciekawych wyzwań, których w zwykłych CRUDach nie ma. No bo tam jest taki stan, który wydaje sie wygodny czyli np. aktualne położenie kulki i jakoś monady mnie się tu nie widzą. Można by zawczasu "obliczyć" sumylację i później ją odpalić wtedy niejako kolejne klatki byłyby kolejną wersją niemutowalnego stanu na stosie czy czymś takim i w sumie tak historia ruchu (te czerwone kropki) jest wyliczana.

Ale z drugiej strony na courserze przy okazji zeszłorocznego kursu o Reactive Application było coś o sygnałach i "Functional Reactive Programming". Także też ciekawe.

Testowanie

Tę część z jednostkami to nawet i zwykłym ScalaTestem da się pojechać bo to zwykła taka Scala-Scala. Można Scalachecka użyć do Property Based Testing no i są specjalne narzędzia które testy tłumacza na javascript i odpalają je już na takim "RuntimeDom"

Kulka

Jest pod adresem :

adres kulki.

Niestety jak wstawiłem iframe to blogger mi powiedział, że nie ma tego ficzeru. Generalnie chyba najfajniej wygląda jak wpisze się 100 w promień i 150 w prędkość poziomą. Mozna tez sobie potestować podawanie złych wartości - Try daje radę przy podawaniu liter zamiast cyfr a dalej już nie testowałem.

Jak na kogoś kto ostatni raz javascripta robił w 2013 i teraz taka kulka... no czuję się taki szcześliwy...

czwartek, 4 sierpnia 2016

I nawet dla ciebie jest nadzieja w webie

Już na samym początku - aby uniknąć niepotrzebnych dyskusji o tym, który młotek jest najlepszy - przywołamy magiczne słowo "warunkowe". Z matematki taki zapis : P(B|A)=... , określa, że będziemy liczyć wystąpienie prawdopodobieństwa B pod warunkiem, że zaszło zdarzenie A. I dalej idąc w bardzo luźną analogie rozpatrzymy dzisiaj sytuację : U(scala.js|scala) czyli

U - szansa, że uda ci się coś zrobić w scala.js jeśli już robisz w scali 
i nie masz czasu co trzy godziny uczyć się nowego frameworka javascriptowego.

Czas i (mała) przestrzeń

Czasem w niektórych firmach istnieje niewidzialny klej, który przytwierdza ludzi do jednego projektu na całe ich życie (a w kontrakcie jest zapisane, że po śmierci ich prace będą kontynuować ich dzieci bo nikt inny nie skuma tego kodu). Często poletko, którym się zajmują ma 1 metr kwadratowy i pomija wiele ciekawych aspektów programowania. I tak jak ktoś bijący się z przeglądarką może znać wątki ale tylko te z mody na sukces. A ktoś działający na back-endzie widzi javascript jako doskonałe narzędzie do wyświetlania alertów.

Ponieważ sam ostatnie półtora roku miałem takie, że główny kontakt z webem był gdy dokumentacje do sparka otwierałem w przeglądarce także jeśli istnieje szansa na robienie weba przy pomocy ekosystemu, który jako tako znam to zamiast ogarniać całą osobną gałąź przemysłu od początku (jakieś grunty srunty) to takiej możliwości warto się przyjrzeć.

To nie jest tutorial bo tutorial już jest : http://www.lihaoyi.com/hands-on-scala-js/. Chodzi bardziej o wrażenia. Wrażenia i odczucia a może nawet i uczucia. To nieprawda, że programiści nie mają uczuć. ja np. często czuję, że coś nie zadziała.

robić Łeba

Bardzo dobry tutorial na wstęp jest tutaj : https://www.scala-js.org/tutorial/basic/ i jak to tutorial - działa dopóki robi się wszystko według tutoriala. Ale warto trochę zbaczać i psuć bo wtedy można zrozumieć to co się robi w szerszym kontekście.

Ten tutorial tworzy prostą stronkę z javascriptem. Może i warto na początek za dużo nie kombinować i go po prostu zrobić ale dalszą ciekawsza wariacją będzie próba takiego para-produkcyjnego użycia scala.js w aplikacji webowej. Do tworzenia wara użyjemy scalatry. Scalatra jest bardzo przyjemnym narzędziem bo już chyba ku*wa prostszego frameworku do tworzenia aplikacji webowej wymyślić nie można.

Dodatkowy zysk edukacyjny pracy na scalatrze jest taki, że łatwiej zrozumieć jak współdziałają te wszystkie pluginy SBT. W Playu wszystko jest takie od razu scalone - "which is good" - ale można przez tę fasadę nie dojrzeć wielu szczegółów.

Scala.js ma swój plugin, który kompiluje do "targetu" a Scalatra siedzi na XSBT-WEB, który zbiera javascript z katalogu "src/main/webapp". Jest kilka szablonów na necie, które pokazują jak zgrać prawidłowo te dwa narzędzia (na przykład : ) zazwyczaj w postaci dwóch niezależnych modułów.

Na potrzeby bloga wybiorę prostsze podejście czyli kopiowanie plików na pałę.

val kopiujNaPałęJavascript: TaskKey[String] =taskKey[String]("compileJavascript")

kopiujNaPałęJavascript := {
  val jsHome = sourceDirectory.value / "main" / "webapp" / "js"
  val dependenciesFileName = "scalatra-js-jsdeps.js"

  val generatedFile: File =(fastOptJS in Compile).value.data
  val generatedDepFile=new File(generatedFile.getParent,dependenciesFileName)

  val destinationFile = jsHome / generatedFile.getName
  val dependenciesFile = jsHome / dependenciesFileName

  IO.copyFile(generatedFile,destinationFile)
  IO.copyFile(generatedDepFile,dependenciesFile)

  jsHome.getAbsolutePath
}

"scalatra-js-jsdeps.js" to plik z wszyskimi zależnościami takimi jak jquery. A no bo właśnie - można sobie zainstalować fasadę na jquery i mieć takie "w-pół" statyczne typowanie w tym jakże wygodnym frameworku.

jQuery("#demoButton").click(processInput _)

def processInput():Unit={
....   
}

No i to też się do testowania przyda później. Apropo JQuery to jeszcze taka dygresja

Dygresja o rzeczach niefizycznych

W arcy ciekawej książce Antifragile Nicholas Taleb opisuje koncepcję żywotności rzeczy niematerialnych, która wybiega trochę na przeciw naszej intuicji. Dzieje się tak gdyż w przypadku rzeczy materialnych im dłużej dana rzecz istnieje tym prawdopodobnie będzie istnieć krócej. I tak np. mamy 1 letni i 10 letni samochód tej samej marki - intuicja nam podpowiada, że - wyłączając zdarzenia losowe - ten starszy szybciej pójdzie na złom.

Teraz weźmy rzecz niematerialną jak choćby język programowania. Mamy dwa języki - jeden wydany rok temu a drugi przed 10ciu laty. Pytanie, który wcześniej się rozpłynie w nicości. No i tutaj może być trudniej to ogarnąć bo generalnie język jako koncept nie rdzewieje, nie wpieprzają go korniki ani nic takiego. żywotność konceptu jest zależna tylko od nośnika i jeśli jakieś nośniki od 10 lat przenoszą ten koncept to oznacza, że z jakiegoś powodu wygrał w ruletce "selekcji naturalnej" języków programowania. A ten co ma rok czy nawet miesiąc - nie wiadomo.

No i mamy takie JQuery, które już ma 10 latek i przetrwało selekcje a 10 lat temu był w użyciu też inny wynalazek - prototype.js. Na przełomie 2006/2007 był jednym z niewielu szczepionek na popularną w webdeveloperce chorobę zwaną IE6 (gimby nie znajo i ich kurwa szczęście...). A teraz według wiki używa go 2,2% stron czyli pewnie twórca, jego sąsiad i kilka korpo z miód-maintenecem. Na wiki możecie sobie poczytać co poszło nie tak.

Także jak z dnia na dzień zobaczysz jakiś super nowy hiper frejmowrk weź wdech, dokończ robote i w wolnej chwili na spokojnie się temu przyjrzysz.

Moje pole

Celem laboratorium będzie walidacja jednego pola po stronie klienta, wysłanie go na serwer , ponowna validacja i odesłanie jakiejś odpowiedzi.

Nie ma sensu wklejać całego kodu bo to luźno zwisający HTML i kilka instrukcji scali także skoncentrujemy się na najważniejszych fragmentach. No i tak na poniższym wycinku rejestrujemy "onklika" , po którym zgarniemy wartość z pola i przy pomocy scalowego (a jakże) pattern matchingu sparsujemy wartość i podążymy jedną z dwóch ścieżek : błędu lub szczęścia.

jQuery("#demoButton").click(processInput _)

def processInput():Unit={
    jQuery("#wynik").remove()
    val mojePole: Dynamic =jQuery("#mojePole").value()

    Try(mojePole.toString.trim.toInt) match {
      case Success(v) => validInput(v)
      case Failure(e) => wrongInput(e)
    }
}

Jak sie popsuło to będzie na czerwono :

 def wrongInput(e: Throwable)= {
    jQuery("body").prepend(s"""
         |<div class="alert alert-danger" role="alert" id="wynik">ERROR : ${e}</div>
         """.stripMargin)
  }

Do serwera

I oto dwa kawałki kodu napisane w jednym i tym samym silnie typowanym języku siedzące w dwóch różnych miejscach internetu (w teorii bo w praktyce w tym przykładzie oba są na moim kompie) komunikują się ze sobą!

//scala.js - browser
Ajax.post(s"/validate?value=$v").onComplete{
      case Success(xhr) => prependResponse(xhr.responseText)
      case Failure(e) => prependResponse(s"zjebało się : $e")
}


//scalatra-serwer
post("/validate"){
    Try(params("value")).map(v=>
      Ok(s"z serwera $v")
    ) getOrElse halt(BadRequest())  //400
}

Scalatra jak mówiłem prosta i przyjemna także taki prosty kodzik obsługuje post. No i wynik :

I działa

Błędy w runtime

W runtime działa już sobie javascript, który w teorii powinien być dobry jeśli tylko został wygenerowany z kompilującej się scali (plus pewnie bliżej nieokreślone N nieprzewidzianych bugów). Natomiast nic się nie dzieje gdy wyszukuję nieistniejący element na stronie i coś tam działam - w sensie nie dzieje się na chromie bo w testach ładnie się wywala. A testy pisze się np. w tym : https://github.com/lihaoyi/utest czyli narzędzia są.

Wygląda obiecująco