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.

***

Brak komentarzy:

Prześlij komentarz