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.