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