Z tych dwóch tutków i jakiś tam video na yt czytałem i oglądałem sobie i naszło mnie kilka rozkmin, które pewnie rozwiane przez Was pomogą mi w dalszej przygodzie z tym językiem. Otóż, w którym miejscu Scala ma przewagę nad Javą ? No ok, są operacja na strumieniach, ale to przecież w javie 8 weszło i działa tak samo. Są klasy przypadków dostarczające za free implementaje equals i hashCode. I co jeszcze ?
Najpierw trzeba sobie zadać pytanie czym jest programowanie funkcyjne. Wikipedia w zasadzie opisuje to w miarę OK:
In computer science, functional programming is a programming paradigm—a style of building the structure and elements of computer programs—that treats computation as the evaluation of mathematical functions and avoids changing-state and mutable data. It is a declarative programming paradigm, which means programming is done with expressions[1] or declarations[2] instead of statements. In functional code, the output value of a function depends only on the arguments that are passed to the function, so calling a function f twice with the same value for an argument x will produce the same result f(x) each time; this is in contrast to procedures depending on a local or global state, which may produce different results at different times when called with the same arguments but a different program state. Eliminating side effects, i.e. changes in state that do not depend on the function inputs, can make it much easier to understand and predict the behavior of a program, which is one of the key motivations for the development of functional programming.
Jak widać sedno programowania funkcyjnego to wcale nie żonglowanie funkcjami, tylko operowanie na niemutowalnych danych. Funkcje wyższego rzędu w językach funkcyjnych są po to, by zredukować powtarzający się kod. Natomiast (do niedawna?) np w takim Node.js funkcje wyższego rzędu (tzn funkcje przyjmujące callbacki) są używane dlatego, że JavaScript oferuje tylko jeden wątek i używanie callbacków jest niejako wymuszone (nie ma żadnego awaita w JSie).
Scala ma pełno właściwości, które czynią programowanie funkcyjne w Scali znacznie bardziej rozsądnym niż w Javie:
- Scala ma osobną hierarchię kolekcji, w tym kolekcji niemutowalnych z wydajnymi operacjami podziału i łączenia, np:
- prepend dla Listy w Scali jest wykonywany w czasie stałym
- podobnie wyciągnięcie tail z Listy jest w czasie stałym (oba powyższe są możliwe dlatego, że Lista w Scali to lista pojedynczo łączona)
- wstawienie (bądź usunięcie) elementu do niemutowalnego Seta tworzy drugiego Seta w czasie logarytmicznym
- podobnie z Mapą i Vectorem (w tych przypadkach można podmienić element w Mapie czy Vectorze z taką samą złożonością)
- wszystko powyższe jest możliwe dzięki structural sharing - nowa niemutowalna kolekcja powstała z dołączenia czy usunięcia elementu współdzieli ze starą kolekcją dużą część danych
- case class jest domyślnie (bo można wstawić słówko
var
i móc podmieniać pola) niemutowalna, zawiera metodę copy dla łatwego tworzenia podobnych instancji oraz gotowego companion objecta z ekstraktorem
- niemutowalność case class oraz typowo używanych kolekcji sprawia, że można je dowolnie przesyłać między wątkami i nie martwić się o synchronizację
- w Scali używanie wątków wprost jest rzadko spotykane i używa się aktorów - w mechaniźmie aktorów silnie zaleca się używanie niemutowalnych wiadomości, a niemutowalne kolekcje i struktury danych są świetnymi rodzajami wiadomości do przesyłania między wątkami/ aktorami/ etc
- bardzo zwięzłe (nawet w porównaniu z Javą 8) sposoby zapisu funkcji
- ekstraktory i dopasowanie wzorców umożliwia typowe dla języków funkcyjnych oddzielenie struktur danych od funkcjonalności - czasami jest to znacznie wygodniejsze niż OOP, a Scala umożliwia mieszanie wielu stylów, w zależności od tego który jest dla nas w danym momencie wygodniejszy
- wbudowane w kompilator wsparcie dla optymalizacji rekurencji ogonowej - rekurencja ogonowa to często fundament czysto funkcyjnych algorytmów, bo daje te same możliwości co imperatywne konstrukcje, a zachowuje matematyczną czystość
- itd
Książka https://www.manning.com/books/functional-programming-in-scala wprowadza w świat programowania funkcyjnego z użyciem Scali. Pamiętam, że było tam fajne tłumaczenie czym jest programowanie funkcyjne i skupiono się w pewnym momencie na 'local reasoning'. Mając niemutowalne obiekty nie musimy zastanawiać się czy dane wywołanie funkcji z interesującym nas obiektem jako parametrem zmieni ten obiekt. To sprawia, że jeśli chcemy się dowiedzieć skąd się wzięły dane w naszym obiekcie wystarczy cofnąć się do miejsca w którym został stworzony - i tyle. Zamiast sprawdzać które funkcje mogą zmienić stan naszego obiektu (a w magicznych imperatywnych frameworkach obiekt może być modyfikowany nawet z innego wątku i z zupełnie innego miejsca w kodzie) wystarczy, że prześledzimy historię tworzenia naszego obiektu i wcześniejszych obiektów.
Dodatkowo Scala ma pewną cechę odróżniającą ją od innych języków - parametry i konwersje implicit. Te rzeczy dają dużo możliwości na zredukowanie nadmiarowego kodu (jak się trochę pokodzi w Scali to się zrozumie), ale niektórzy kręcą nosem widząc implicity. IntelliJ ma w miarę OK wsparcie dla implicitów w Scali (Ctrl+Alt+P pokazuje implicit parametry, Ctrl+Alt+Q pokazuje implicit konwersje).
Druga sprawa dotyczy tego w jaki sposób ogarnąć kiedy dobrze użyć danej funkcjonalności Scali ? Bo tam jest sporo tego, np ekstraktory, dopasowanie wzorców i inne nowe dla mnie pojęcia. I jak tak myślałem, to na każdym kroku trzeba rozkminiać czy pisać w tej Scali po Javovemu czy użyć czegos "Scalowego" ? I czy to jest tak, że jak już piszemy w Scali to wszystko w Scali czy jedne rzeczy idą w Javie a reszta Scala ?
Kod typowo Scalowy raczej nie przypomina Javowego. Co innego gdybyś integrował się z Javowymi bibliotekami - wtedy Javizmy siłą rzeczy się pojawią. Scala wspiera równie mocno OOP co FP, więc kod Scalowy może wyglądać różnie. Ogólnie jednak typowy kod Scalowy jest pewnym kompromisem - z jednej strony kładzie się duży nacisk na niemutowalność przy operowaniu na kolekcjach czy własnych strukturach danych, a z drugiej strony opakowywanie wszelkich funkcji z efektami ubocznymi w rzeczy typu monada I/O jest uważana za przesadę (mimo, że np w Haskellu monada IO to norma). Do izolacji efektów ubocznych w Scali używa się zwykle systemu aktorów (czyli Akki).
Jak poklepiesz trochę w Scali, poczytasz kod Scalowy pisany przez (w miarę) doświadczonych Scalowców to z czasem załapiesz czym jest idiomatyczny Scalowy kod. Nie ma się co spinać. Podobnie jest przy każdym innym języków. Jeśli wyrwiesz Javowca i zmusisz go do pisania Pythona to możesz dostać kod Pythonowy z getterami i setterami wprost z Javowych fasolek. Z czasem jednak jego kod będzie stawał się coraz bardziej pythonic
.
I jeszcze jedno.. gdzieś wyczytałem, że jak się zdecyduję pisać w Scali to lepiej w Play niz Spring ?
Scali nie łączy się z krowiastymi Javowymi frameworkami. Używa się natomiast Javowych bibliotek jeśli nie ma wygodnych Scalowych odpowiedników. Różnica między frameworkiem, a biblioteką jest taka, że bibliotekę można użyć lokalnie i w małym stopniu (np tylko interesujących nas kilka metod z biblioteki), natomiast framework wymusza na nas szerokie zastosowanie jego rozbudowanego API.