POIT #234: Software Craftsmanship: Functional programming

Witam w dwieście trzydziestym czwartym odcinku podcastu „Porozmawiajmy o IT”. Tematem dzisiejszej rozmowy w serii podcastów o software craftsmanship jest functional programming czyli programowanie funkcyjne.

Dziś moim gościem jest Łukasz Drynkowski, z którym mam przyjemność współtworzyć portal z ofertami pracy dla branży IT o nazwie SOLID.Jobs.

Główne myśli o programowaniu funkcyjnym z tego odcinka to:

  • dobierzcie narzędzie do problemu,
  • zróbcie reaserch na temat programowania funkcyjnego,
  • tam gdzie to ma sens, stosujcie elementy functional programming (np. immutability, pure functions) w językach z dominującym paradygmatem obiektowym.

Subskrypcja podcastu:

Linki:

Wsparcie na Patronite:

Wierzę, że dobro wraca i że zawsze znajdą się osoby w bliższym lub dalszym gronie, którym przydaje się to co robię i które zechcą mnie wesprzeć w misji poszerzania horyzontów ludzi z branży IT.

Patronite to tak platforma, na której możesz wspierać twórców internetowych w ich działalności. Mnie możesz wesprzeć kwotą już od 5 zł miesięcznie. Chciałbym oddelegować kilka rzeczy, które wykonuję przy każdym podcaście a zaoszczędzony czas wykorzystać na przygotowanie jeszcze lepszych treści dla Ciebie. Sam jestem patronem kilku twórców internetowych i widzę, że taka pomoc daje dużą satysfakcję obu stronom.

👉Mój profil znajdziesz pod adresem: patronite.pl/porozmawiajmyoit

Pozostańmy w kontakcie:

 

Muzyka użyta w podcaście: „Endless Inspiration” Alex Stoner (posłuchaj)

Transkrypcja podcastu

To jest 234. odcinek podcastu Porozmawiajmy o IT, w którym w cyklu rozmów z Łukaszem Drynkowskim z portalu z ogłoszeniami pracy IT SolidJobs, który zresztą mam przyjemność współtworzyć, dyskutujemy o software craftsmanship, czyli o rzemiośle programisty.

Zapraszamy do słuchania i komentowania. A już teraz miłego słuchania.

Odpalamy!

 

Cześć, Łukasz! 

 

Cześć, Krzysztof! 

 

Słyszymy się już w czwartym odcinku serii podcastów o software craftsmanship. Ostatnio mówiliśmy o programowaniu obiektowym. Dzisiaj mam wrażenie, że będziemy mówić o rywalu. To się może okaże, czy rywalu, czy uzupełnieniu programowania obiektowego, którym jest programowanie funkcyjne, które zyskuje od pewnego czasu na popularności, trochę miesza się w językach programowania z programowaniem podejściem obiektowym. Dzisiaj zobaczymy sobie, jak ten fakt można wykorzystać. 

Ja natomiast chciałem rozpocząć od takiej ciekawej uwagi, pewnej ciekawostki na temat programowania funkcyjnego, bo wydaje się, że ta rosnąca ostatnio popularność jest właśnie efektem może ostatnich 5 czy też 10 lat. Może się wydawać, że programowanie funkcyjne jest pewnego rodzaju nowością, która gdzieś tam zawitała w językach programowania. Tymczasem jest zupełnie odwrotnie. 

Tutaj taka analogia, którą mogę powiedzieć, która pasuje do tej sytuacji, jest związana z autami. Przeżywamy boom, rozkwit aut elektrycznych i wydaje nam się, że to jest taka nowinka technologiczna. Tymczasem pierwsze auta były właśnie autami elektrycznymi i te silniki były właśnie silnikami elektrycznymi. I tak samo jest w przypadku programowania. Pierwsze języki programowania, jeśli w ogóle tak można o tym powiedzieć, takie teoretyczne wręcz języki programowania opierały się właśnie na programowaniu funkcyjnym. Programowanie obiektowe nastąpiło dużo później, tak że dzisiaj przeżywamy, mam wrażenie, powrót programowania funkcyjnego, a nie narodzenie się tej koncepcji. 

Mimo tego, jakby nie spojrzeć, jakby popatrzeć na popularność języków programowania, to dominuje tam oczywiście podejście obiektowe. Jestem ciekawy, Łukasz, co ty o tym myślisz. Dlaczego programowanie funkcyjne, pomimo tego, że starsze, jest mniej popularne niż to obiektowe? 

 

Myślę, że po prostu popularność paradygmatu obiektowego wynika z tego, że jest to dla nas bardziej naturalne w sposobie myślenia. Po prostu myślimy obiektami, myślimy tymi zależnościami, że ktoś jest rodzicem, ktoś jest dzieckiem, kwadrat jest prostokątem. To są po prostu te nasze mapy myślowe, z którymi na co dzień jakby obcujemy i po prostu kognitywnie jest nam trudniej się wpasować w ten model funkcyjny. 

Ja pamiętam też tutaj ze studiów, co prawda programowanie deklaratywne, ale powiedzmy, że to już leży dość blisko, to jak mieliśmy właśnie zacząć programować w jednym z języków deklaratywnych, to było takie, żeby w ogóle zmienić ten sposób myślenia. Najtrudniejszy był ten początek, żeby w ogóle zacząć programować. A jeśli chodzi o programowanie obiektowe, to myślę, że to nam przychodzi tak naturalnie. I tutaj bym szukał tej popularności języków obiektowych. 

Też drugi czynnik, myślę, języki funkcyjne są bardziej formalne, bardziej sformalizowane i dają więcej ograniczeń dookoła, co niekoniecznie jest oczywiście złą rzeczą, o czym tutaj opowiemy. Natomiast w języku obiektowym ta dowolność jest dużo większa i sami sobie musimy przed sobą jakby stawiać różnego typu ograniczenia, żeby po prostu ten program nam się nie rozlazł, że tak się wyrażę. Jest dużo łatwiej zrobić pewnie spaghetti code w takich właśnie językach, gdzie ten stopień formalizmu jest jednak trochę niższy. 

 

Tak, to właśnie tłumaczenie, które przedstawiłeś, że obiekty to jest to, z czym mamy w życiu takim codziennym do czynienia i z tego wynika właśnie większa popularność podejścia obiektowego, to jest jedna z teorii, która mówi o tym, dlaczego to programowanie obiektowe jest bardziej popularne, ale ja się spotkałem jeszcze z takimi tłumaczeniami, że np. programowanie obiektowe jest częściej wykładane, czy też w większej objętości jest uczone i wykładane na uczelniach. 

 

Ale tutaj to jakiś problem jajka i kury w takim razie mamy. 

 

Dokładnie. To jest jedno z takich tłumaczeń, a jeszcze inne próbuje ten problem rozwiązać historycznie. Te wcześniejsze języki, mówię tutaj o C i jeszcze wcześniejszych, nie były obiektowe, tam nie było tej obiektowości, albo przynajmniej jakieś jedynie podstawowe koncepty się znajdowały, ale był taki czas, początek lat 80, kiedy nagle pojawił się C++ jako nakładka na C i mówi się, że od tego momentu programowanie obiektowe rozkwitło. A programowanie obiektowe rozumiane w takim znaczeniu, że języki po prostu kolejne, które powstawały po C++ już miały tę obiektowość jakby wbudowaną jako taki główny paradygmat. Java, C Sharp, Python itd. 

Ale tutaj jest ciekawa taka rzecz, takie tłumaczenie, które mówi o tym, że być może to upowszechnienie obiektowości w C++ i dalej było trochę efektem ubocznym, a nie głównym powodem, dlaczego obiektowość później po prostu zyskała na popularności, ponieważ C++ oprócz obiektowości wprowadzał też bardzo wiele udogodnień dla programistów C. Łatwiejsza obsługa błędów, automatyczne zarządzanie zasobami i wiele, wiele innych feature’ów. Więc jak gdyby upowszechniał się język C++ z licznych powodów, a nie tylko dlatego, że miał tę obiektowość. Ale ponieważ miał tę obiektowość, to automatycznie jak gdyby coraz więcej programistów zaczęło tę obiektowość stosować i dzięki temu tak troszkę można powiedzieć przy okazji, to podejście obiektowe zaczęło się upowszechniać. 

Na różne sposoby możemy sobie właśnie tę większą popularność programowania obiektowego tłumaczyć. Pewnie prawda leży jak zwykle gdzieś tam pośrodku i wiele czynników na to wpłynęło, natomiast fakt, że modelujemy w oprogramowaniu obiektowym właśnie rzeczywistość tą biznesową, korzystając z obiektów, może być oczywiście tym czynnikiem, który powoduje, że po prostu łatwiej jest nam problem biznesowy na kod przełożyć. 

Ale tutaj jest ciekawa taka rzecz, takie tłumaczenie, które mówi o tym, że być może to upowszechnienie obiektowości w C++ i dalej było trochę efektem ubocznym, a nie głównym powodem, dlaczego obiektowość później po prostu zyskała na popularności, ponieważ C++ oprócz obiektowości wprowadzał też bardzo wiele udogodnień dla programistów C. Łatwiejsza obsługa błędów, automatyczne zarządzanie zasobami i wiele, wiele innych feature’ów. Więc jak gdyby upowszechniał się język C++ z licznych powodów, a nie tylko dlatego, że miał tę obiektowość. Ale ponieważ miał tę obiektowość, to automatycznie jak gdyby coraz więcej programistów zaczęło tę obiektowość stosować i dzięki temu tak troszkę można powiedzieć przy okazji, to podejście obiektowe zaczęło się upowszechniać.

 

Myślę, że tutaj jeszcze jeden czynnik bym znalazł. Mianowicie my jako programiści naturalnie lubimy dochodzić do sedna rzeczy i wiedzieć, jak coś działa, rozkręcić tam wszystkie śrubki i zobaczyć po prostu krok po kroku, jak coś się wykonuje, dlaczego tak, może to zoptymalizować. I takie programowanie imperatywne czy obiektowe daje nam tą możliwość, a jednak przy takim programowaniu bardziej deklaratywnym to bardziej mówimy, co byśmy chcieli tutaj osiągnąć, a nie wiemy, jak tam ten SQL tak naprawdę np. robi to, że zwróci nam te dane z tabelki czy zapyta ten indeks, czy inny. Co tam się stanie? Jesteśmy w stanie to oczywiście sprawdzić, ale co do zasady, to tutaj jest to trochę czarna skrzynka. Mówimy, co chcemy i dostajemy. 

I w takiej koncepcji ta nasza taka naturalna ciekawość świata, ciekawość tego, jak coś działa, to nie jest zaspokojona. I myślę, że tutaj też bym się gdzieś dopatrywał jakiegoś takiego czynnika, że właśnie chcemy być przy kontroli, wolimy kontrolować, wiedzieć, jak coś się stanie. 

 

Właśnie, też myślałem o tej kontroli, że w takim podejściu imperatywnym, kiedy mówimy krok po kroku, wydajemy takie proste instrukcje, to jesteśmy prawie że stuprocentowo pewni, że ta maszyna wykona właśnie dokładnie to, co chcemy, ponieważ przez te wszystkie kroki przejdzie w programowaniu deklaratywnym, którego gdzieś tam podzbiorem jest właśnie programowanie funkcyjne. My raczej używamy pewnych takich, jak powiedziałeś, czarnych skrzynek. W najlepszym przypadku łączymy ileś tam tych czarnych skrzynek i mamy nadzieję, że jak uruchomimy program, to na końcu otrzymamy ten efekt, na którym nam zależało. 

 

À propos tej kontroli, jeszcze może opowiem krótki żart. To tak naprawdę mema fajnego widziałem wczoraj w internecie. To mniej więcej leciało tak: Programowanie w 50% składa się z pisania kodu, a pozostałe 90% to debugowanie. 

 

Ja bym powiedział, że jeszcze więcej, przesunąłbym nawet ten suwak jeszcze bardziej, ale coś tym jest. Właśnie, mówiliśmy tutaj, że programowanie funkcyjne nieco inaczej modeluje tę naszą rzeczywistość, nie tak jak w OOP nie mamy obiektów, natomiast ja tutaj lubię przywoływać taką analogię do taśmy produkcyjnej w fabryce, gdzie mamy robota, który dostaje jakieś dane wejściowe, można powiedzieć, np. część samochodu, coś tam z tą częścią wykonuje, coś tam jakieś przetwarzania tych danych robi i podaje efekt swojej pracy do następnego robota itd. 

Tymi robotami są oczywiście funkcje, które wykonują jakąś właściwą dla siebie pracę, jakiś algorytm wykonują i podają dalej efekt swojej pracy. W najprostszym wydaniu można powiedzieć, że programowanie funkcyjne modeluje świat i w związku z tym mamy dwa istotne elementy. Przede wszystkim funkcje, które są takim trzonem, programowanie funkcyjne stąd bierze swoją nazwę. A po drugie, coś co odróżnia od programowania obiektowego, to brak współdzielonego stanu. 

Właśnie wracając tutaj do tej analogii, dostajemy jakieś dane na wejściu, jakaś funkcja dostaje te dane na wejściu, przetwarza je i podaje dalej. To jest tutaj ta koncepcja takiego strumieniowego, można powiedzieć, przetwarzania danych. I właśnie, i jak tutaj jesteśmy przy tym takim temacie podstawowych zagadnień związanych, konceptów związanych z programowaniem funkcyjnym, no to oczywiście funkcje, które są takim, można powiedzieć, pełnoprawnym uczestnikiem First Citizen… 

 

To może opowiedz nam, Krzysztofie, czym się taka funkcja w języku funkcyjnym różni od metody w jakimś obiekcie. 

 

Przede wszystkim funkcja może istnieć niezależnie od klasy czy obiektu, w odróżnieniu od metod, które są dowiązane, można powiedzieć, do obiektu, co wynika z tego, że pochodzą z jakiejś tam klasy. Funkcja jest takim, można powiedzieć, pełnoprawnym robocikiem, który wykonuje roboty, pewne prace, pewne przetwarzanie danych, pewne operacje i jak gdyby funkcjonuje jako niemalże typ w programowaniu funkcyjnym. Jest właśnie tym first citizen elementem naszego programowania. 

To znaczy, że taką funkcję możemy sobie przypisać do jakiejś zmiennej, możemy ją przekazać dalej, jest ona na tym samym poziomie istotności jak zwykła zmienna. 

Dodatkowo mamy tutaj tzw. high order functions, czyli takie funkcje, które mogą przyjmować też inne funkcje jako argumenty, bądź też same zwracać funkcje. W związku z tym możemy wejść na jeszcze wyższy poziom abstrakcji, bo w tej naszej analogii to roboty tworzyłyby inne roboty, które wykonywałyby dalszą pracę – tak możemy sobie jeszcze ten poziom abstrakcji rozszerzyć. 

Jeśli jesteśmy tutaj już przy funkcjach, to mamy też mocne wykorzystanie rekurencji, czyli funkcja, która wywołuje samą siebie po to, żeby rozłożyć ten większy problem na mniejsze problemy. Wiele języków właśnie tych funkcyjnych jest zoptymalizowane na takie uruchamianie tych funkcji rekurencyjnych, żeby nam stosu nie przepełnić, żebyśmy w pewnym momencie całej pamięci sobie nie zajęli. To jest jak gdyby taka główna zaleta tego programowania funkcyjnego. 

Na tym się oczywiście temat funkcji w programowaniu funkcyjnym nie kończy, bo takim podstawowym konceptem, swego rodzaju ideałem, bo tego nie da się jak gdyby zawsze uzyskać, są czyste funkcje, pure functions. 

Znowu wracając do tej naszej analogii, to są takie funkcje, które tylko dostają coś na wejściu, przetwarzają i dają dalej wynik. W żaden sposób nie dotykają niczego poza tymi danymi, czyli nie mają tych właśnie efektów gdzieś tam poza swoim działaniem. Te czyste funkcje, do których dążymy, są na tyle korzystne, że bardzo łatwo jest je testować, jeśli nie dotykają niczego innego, tylko te dane, które dostały na wejściu. Łatwo je debugować. Jesteśmy w stanie uzyskać tzw. idempotencję, czyli uruchomienie tej funkcji z tym samym argumentem, z tymi samymi danymi na początku da nam zawsze tę samą wartość na zewnątrz, bo nie jesteśmy zależni od świata zewnętrznego. 

 

Tak, i jeszcze wielokrotne wywołanie tej samej funkcji. Ten sam wynik jakby nie podbije wyniku, tak? 

 

Dokładnie, jeśli wykorzystamy ten właśnie element, czy tę naszą własność, to uzyskujemy coś takiego, co się nazywa referential transparency, czyli jesteśmy w stanie zastąpić wywołanie funkcji z pewnym argumentem, jej wynikiem. Jeśli wiemy, że zawsze ta sama dana wejściowa przetworzona przez tę funkcję da nam zawsze określoną wartość na zewnątrz, to wręcz możemy te przeliczenia pominąć i zastosować, bazując na jakimś cache’u czy tego typu rzeczach, zastąpić od razu wejście wyjściem bez przetwarzania. Oczywiście pewnie nie musimy mówić, że to ma liczne korzyści związane z performance’em. 

Dokładnie, jeśli wykorzystamy ten właśnie element, czy tę naszą własność, to uzyskujemy coś takiego, co się nazywa referential transparency, czyli jesteśmy w stanie zastąpić wywołanie funkcji z pewnym argumentem, jej wynikiem. Jeśli wiemy, że zawsze ta sama dana wejściowa przetworzona przez tę funkcję da nam zawsze określoną wartość na zewnątrz, to wręcz możemy te przeliczenia pominąć i zastosować, bazując na jakimś cache’u czy tego typu rzeczach, zastąpić od razu wejście wyjściem bez przetwarzania. Oczywiście pewnie nie musimy mówić, że to ma liczne korzyści związane z performance’em.

 

Tak, czyli żonglujemy między procesorem a pamięcią. 

 

Właśnie. I mówiłem, że to jest taka sytuacja oczywiście idealna, no nie da się całego programu takiego realnego, rozwiązującego jakiś problem biznesowy w obecnym świecie oprzeć o czyste funkcje, bo musimy co jakiś czas coś wrzucić do bazy, coś odczytać z bazy, wywołać jakieś API, więc nie zawsze jesteśmy w stanie napisać funkcje, które będą tylko przetwarzały tą daną wejściową, bazując na swoim algorytmie, bez zasięgania albo bez wysyłania jeszcze gdzieś rzeczy, natomiast im więcej takich czystych funkcji w naszym programie, tym łatwiej się to testuje i program jest po prostu bardziej stabilny. 

Co tutaj jeszcze? Oczywiście znowu po raz kolejny wrócę do tej analogii naszej linii produkcyjnej tych robotów. Jesteśmy w stanie przeprowadzić tzw. function composition, czyli składamy sobie program, łącząc roboty jeden z drugim. Myślę, że wielu pewnie ze słuchaczy gdzieś tam w systemach linuxowych czy unixowych korzystało z takiego pipe’a, takiego operatora w terminalu, który umożliwia przekierowanie wyjścia jednego programu do wejścia drugiego programu. I to jest właśnie function composition, czyli układamy sobie ten nasz program, po prostu łącząc, można powiedzieć, te klocki, takie te małe mechanizmy, które coś tam robią i podają dane dalej. To są te różne cechy, czy te różne właściwości programowania funkcyjnego związane z funkcjami… 

 

Czyli przysłowiowym sendmailem przez Emacsa, tak? 

 

Na przykład, w sensie to jest taki program, który miałby dwa elementy, dwa komponenty, ale oczywiście możemy sobie w szczególności tych bloczków takich dokładać wiele. No właśnie, jeśli jesteśmy przy właściwościach programowania funkcyjnego, to nie da się też nie wspomnieć tutaj o niemutowalności, która jest cechą zazwyczaj ratującą, czy też ograniczającą ten czas debugowania. 

Polega to na tym, że obszar w pamięci, który raz sobie jakoś tam zalokujemy, wrzucając określone dane i dowiążemy referencje do tego obszaru w pamięci, np. tworząc tablicę, którą przypisujemy sobie do jakiejś zmiennej, to za pomocą tej referencji nie możemy tego obszaru w pamięci zmienić. W sensie rozszerzyć, zmniejszyć tego rozmiaru tablicy, coś tam dodać, coś tam odjąć. 

 

Czyli w wyniku otrzymamy nowy obiekt. 

 

Dokładnie. Jeśli chcemy to zrobić, zawsze otrzymujemy nowy obszar pamięci. I dzięki temu jesteśmy pewni, że nikt nam nie namiesza w tym obszarze pamięci, nikt tego nie zmieni, co niekiedy jest problemem w przypadku programowania obiektowego, gdzie wszyscy mają dostęp do tego i tak naprawdę później nie wiadomo właściwie, kto to zmienił. 

 

Tak, w szczególności w scenariuszach przetwarzania współbieżnego.

 

Dokładnie, to jest też ta siła programowania funkcyjnego, że łatwo jest ten model świata przełożyć właśnie na np. wielokorową maszynę, gdzie współbieżnie jesteśmy w stanie realizować wiele obliczeń, bazując na tych cechach funkcji, o których mówiłem i właśnie na niemutowalności, która tutaj nam też bardzo pomaga. 

To tyle, jeśli chodzi o teorie. Oczywiście to dla mnie zamyka tych wszystkich zagadnień abstrakcyjno-teoretycznych i ci, którzy chcą sobie gdzieś rozszerzyć, poczytać, to z pewnością szybko natkną się na zagadnienia typu monady i inne tego typu zawiłe rzeczy. Natomiast ja chciałbym to uspokoić. Ja chciałbym tutaj powiedzieć, że to nie jest tak, że my musimy myśleć tak bardzo abstrakcyjnie, wykorzystywać tego typu koncepty w codziennej pracy. 

Pracuję ponad pięć lat w języku funkcyjnym, w Eliksirze i nigdy nie zdarzyło mi się gdzieś korzystać z tak zawiłych i zakręconych zagadnień, więc to absolutnie nie jest tak, że musimy znać się na zaawansowanej matematyce, że musimy czytać jakieś whitepapery związane z programowaniem funkcyjnym i z przetwarzaniem danych. Absolutnie tak nie jest. 

 

Tak, ale wydaje mi się, że jednak jeśli chodzi o programowanie funkcyjne, to ta bariera wejścia jest trochę wyższa jednak niż w przypadku programowania obiektowego. Wiadomo, że żeby dobrze programować obiektowo, też trzeba dużo tutaj włożyć czasu i nauki, natomiast żeby programować jak bądź i żeby dostać jakiś wynik, który będzie spełniał Twoje oczekiwania, to dużo łatwiej jest po prostu zacząć, tak mi się wydaje. 

 

Myślę, że tak. Nie polecałbym takiego języka typowo funkcyjnego zupełnie na początek. Na pewno łatwiej jest rozpocząć od jakiegoś języka obiektowego, to na 100%. 

Ale to jak gdyby nie zamyka nam różnego typu problemów związanych z programowaniem funkcyjnym, oprócz tej wyższej bariery wejścia trzeba być świadomym, że mniejsza popularność języków funkcyjnych powoduje, że mamy też mniejszą liczbę rozwiązań dostępnych w rozumieniu frameworku, bibliotek. 

 

Mówiąc po ludzku będzie mniej tematów na Stack Overflow. 

 

Tak, pewnie mniej osób do pomocy itd. To nas tutaj też sprowadza do takiego zagadnienia, że jest mniej po prostu developerów specjalizujących się i w tym podejściu, i w tym paradygmacie, i w danym języku. I oczywiście to też się przykłada później na różne rzeczy. Jeśli decydujemy się w projekcie właśnie na taki paradygmat, na taki język, to jako właściciele, jako osoby prowadzące musimy być świadomi, że może być trudniej zatrudnić taką osobę. 

 

Tak, ale z drugiej strony tutaj działa prawo popytu i podaży, tak że też ci specjaliści pewnie są w cenie, jeśli jest ich mało, a także tak jak mówiłeś wcześniej, pewnie nikt nie robi systemu w pełni funkcyjnego, raczej to wygląda w ten sposób, że są jakieś mikroserwisy czy serwisy, które po prostu wykonują tę konkretną pracę, którą lepiej jest rozwiązać właśnie w sposób i wtedy można podzielić taką aplikację, że są elementy napisane obiektowo, czy np. ten interfejs użytkownika, a są elementy, które czysto przetwarzają jakieś dane, do którego są po prostu odpowiednie narzędzia dobrane. 

 

Dokładnie, w tej serii podcastów namawiamy ludzi do tego, żeby stosować ten warsztat programistyczny z głową i elementem właśnie takiego podejścia, jest dobór rozwiązania do problemu. Nie chciałbym tutaj powiedzieć, że programowanie funkcyjne jest zawsze najlepsze, albo programowanie obiektowe jest zawsze najlepsze. Jak zawsze powinniśmy to dobrać do problemu. 

Przykładem tego, że programowanie funkcyjne nie zawsze jest najlepszym wyborem jest GameDev. Ta niemutowalność, o której przed chwilą powiedziałem, jako ważna cecha, często bardzo pomocna cecha, w tym przypadku jest problematyczna, bo zobacz, jak sobie patrzysz na taką grę, gdzie modelujemy ją, czy też widzimy ją jako mnóstwo obiektów, które wchodzą w interakcję ze sobą, gdzieś tam strzelasz, gdzieś coś, czar się dzieje, gdzieś coś uderzasz. W bardzo krótkim czasie trzeba zmieniać stan obiektów. Jeśli byśmy tutaj zaaplikowali tą niemutowalność, to by oznaczało, że każda drobna zmiana wymaga zalokowania nowego obszaru w pamięci z tą zmienioną wartością. Więc tutaj mielibyśmy bardzo dużo operacji na pamięci, co jest oczywiście kompletnie nieoptymalne. Znacznie lepiej jest modelować grę, zwłaszcza taką, w której się dużo dzieje właśnie za pomocą obiektów. Więc tak, dobierajmy to rozwiązanie do problemu. 

 

Jeszcze inny ciekawy wątek bym chciał tutaj poruszyć. W związku z tym, że to programowanie funkcyjne jest takie bardziej sformalizowane, to jak myślisz, czy jest większe, nie wiem, czy to dobre słowo, ryzyko tego, że jakby to programowanie funkcyjne zostanie zautomatyzowane przez jakieś AI niż w przypadku programowania takiego imperatywnego? 

 

Powiedziałbym, że chyba odwrotnie. Programowanie imperatywne, czyli takie krok po kroku jest chyba prostsze do zrozumienia dla AI, dla maszyny, tutaj robię w powietrzu cudzysłów, niż programowanie właśnie bardziej deklaratywne, gdzie musimy nieco bardziej pomyśleć i nie ma takich oczywistych rozwiązań. Natomiast nie wiem, czy jest tutaj jakieś ryzyko w ogóle, bo rozmawialiśmy już o tym w poprzedniej edycji. 

 

Takie pytanie tutaj po prostu na czasie. 

 

Jasne, jasne. Więc oczywiście programowanie funkcyjne ma swoje wady, jakby nie było. Ma też swoje zalety, do których z pewnością należy niezawodność. Tutaj ja podeprę się przykładem Erlanga, język, którym się specjalizuje, Elixir działa na maszynie wirtualnej Erlanga, korzystając właśnie z tych dobrodziejstw programowania funkcyjnego, które w Erlangu się znajdują. To jest język bardzo niezawodny, stworzony dla domeny telekomunikacyjnej, gdzie po prostu wszystko musi działać, i ta niezawodność w dużym, bardzo dużym stopniu wynika właśnie z tych cech programowania funkcyjnego, czyli niemutowalności, czyli to, że mamy tzw. lazy evaluation, czyli te funkcje są wykonywane tylko wtedy, kiedy muszą być wywoływane, łatwiej się też tam testuje rzeczy z racji na to, że jesteśmy w stanie sobie taką funkcję właśnie wyciągnąć i w takiej separacji ją można przetestować po prostu. 

Jeśli dążymy do tego, żeby pisać oprogramowanie funkcyjne z tym właśnie podejściem pure functions, czyli nie mamy tych tzw. side effectów, to wtedy łatwiej jest debugować, łatwiej jest testować, mniej jest po prostu szans, że zapomnimy o tym, że gdzieś tam coś na zewnątrz naszego kodu modyfikujemy, generalnie to debugowanie jest wówczas, można powiedzieć, prostsze i też dłużej oprogramowanie działa bez jakichś problemów. 

Natomiast rozmawialiśmy ostatnio już o tym, że ten podział taki sztywny dosyć na programowanie obiektowe i funkcyjne jest być może nieadekwatny, nie przystaje do tego, jakich narzędzi w rozumieniu języków programowania obecnie używamy. Przecież te koncepty się mieszają. 

 

Tak, i języki też ewoluują do kolejnych wersji. Ja tutaj się może moim własnym podwórkiem trochę podeprę, czyli dotnę tym C-Sharpem. Te koncepty funkcyjne tutaj ciągle są wdrażane, że się tak wyrażę, w język. Już jako chyba taki pierwszy język obiektowy już w tym .NET 3.5 dostaliśmy linku, czyli możliwość właśnie takiego funkcyjnego, deklaratywnego oprogramowania. W najnowszych teraz wersjach .NET-a dostaliśmy pattern matching, switch expression, rekordy, to są wszystko koncepcje pochodzące właśnie z języków funkcyjnych i to nie wszystko, nawet też można w C Sharpie oczywiście robić wstawki F#, czyli bezpośrednio ten język funkcyjny wykorzystać. 

Podobna sytuacja jest w JavaScripcie. Są tam te koncepty już czysto funkcyjne, czy też takie mieszane, po prostu już wbudowane w język. Nawet nie musisz używać jakiejś konkretnej dodatkowej biblioteki, tylko to są po prostu koncepcje, które w tym języku już są. I musisz po prostu z tego skorzystać, czy też sobie te ograniczenia sam narzucić. Tak jak tutaj mówiliśmy, że to niezmienność, czyli ten immutability, czy te koncepcje pure functions, czyli funkcji, które nie mają side effectów, to są od dawna takie dobre praktyki, czy też patterny, które się wykorzystuje po prostu w języku obiektowym, tylko trzeba to chcieć zrobić i sobie samemu narzucić te ograniczenia, które nam pozwalają np. pisać lepszy kod współbieżny albo po prostu kod, który będzie łatwiejszy w utrzymaniu, w testowaniu. 

Podobna sytuacja jest w JavaScripcie. Są tam te koncepty już czysto funkcyjne, czy też takie mieszane, po prostu już wbudowane w język. Nawet nie musisz używać jakiejś konkretnej dodatkowej biblioteki, tylko to są po prostu koncepcje, które w tym języku już są. I musisz po prostu z tego skorzystać, czy też sobie te ograniczenia sam narzucić. Tak jak tutaj mówiliśmy, że to niezmienność, czyli ten immutability, czy te koncepcje pure functions, czyli funkcji, które nie mają side effectów, to są od dawna takie dobre praktyki, czy też patterny, które się wykorzystuje po prostu w języku obiektowym, tylko trzeba to chcieć zrobić i sobie samemu narzucić te ograniczenia, które nam pozwalają np. pisać lepszy kod współbieżny albo po prostu kod, który będzie łatwiejszy w utrzymaniu, w testowaniu. 

 

Dokładnie. Oprócz tych języków, które wymieniłeś, to oczywiście Java, Ruby, Kotlin chociażby. Wiele z tych nowoczesnych języków programowania, które obecnie wykorzystujemy, ma już zaszyte koncepty programowania funkcyjnego. Nic nie stoi naprzeciwko, żeby z tego jak najbardziej skorzystać. 

Jakiś czas temu słyszałem coś takiego, że programowanie funkcyjne dobrze sprawdza się w rozwiązywaniu takich mniejszych problemów, natomiast do tworzenia dużych aplikacji, rozległych, zaawansowanych, lepiej jest wykorzystać programowanie obiektowe, ponieważ łatwiej jest zrozumieć wtedy taki duży system. 

Osobiście nie do końca się z tym zgadzam, pracuję obecnie w dosyć dużym projekcie, który jest zrobiony czysto funkcyjnie i absolutnie nie ma tam takich problemów, ale potrafię zrozumieć takie podejście, o którym zresztą też, Łukasz, powiedziałeś, że jakiś tam mikroserwis realizujemy właśnie w tym podejściu funkcyjnym, ponieważ po prostu chociażby rzeczywistość biznesowa lepiej przystaje do takiego odwzorowania funkcyjnego. Nic nie stoi na przeszkodzie, żeby posługując się językiem, który niby pierwotnie jest obiektowy, wykorzystywać właśnie te rozwiązania z programowania funkcyjnego. 

Wydaje mi się, że będziemy mieli coraz więcej przenikania się tych światów i te granice będą się coraz bardziej rozmywać. 

 

Też pewną zaletą, o której tu wcześniej nie powiedzieliśmy, jeśli chodzi o te wstawki funkcyjne w językach obiektowych, to jest zwięzłość tego, co robimy. Te switch expressiony to w porównaniu z takim zwykłym switchem w C-Sharpie po prostu mniej wciśnięć klawiszy na klawiaturze, a wiadomo, że każdy programista w życiu ma ograniczoną liczbę tych wciśnięć, więc oszczędzajmy. 

 

Tak. Myślę, że warto tutaj polecić w ogóle zaznajomienie się z programowaniem czy też konceptami programowania funkcyjnego, ponieważ najprawdopodobniej nawet jeśli siebie definiujecie jako programistów obiektowych, to w języku, który używacie, znajdziecie takie właśnie rozwiązania. Fajnie jest w swoim toolboxie właśnie mieć takie umiejętności, bo to zwyczajnie może nam właśnie bardzo uprościć. 

Tak, i robić to po prostu świadomie, bo tak naprawdę wszyscy jesteśmy programistami funkcyjnymi, ale o tym nie wiemy. 

 

Dobrze, Łukasz, myślę, że przedstawiliśmy tak pokrótce wady, zalety, podstawowe cechy programowania funkcyjnego. Okazało się, że wcale nie musimy tak mocno dzielić świata na AOP i FP. To co, pokusiłbyś się o kilka punktów podsumowania? 

 

Jasne, no to tak jak tutaj mówiliśmy, dobierzcie narzędzia do problemu, unikajcie tych złotych młotków, tych klapek na oczach, tylko właśnie postarajcie się dobrać paradygmat programowania także do tego, co robicie. Oczywiście tu są różne ograniczenia, np. związane z kompetencjami w zespole i różnymi ryzykami, ale jeśli macie taką możliwość i można coś zrobić szybciej, po prostu prościej, to czemu nie skorzystać? 

Tych z Was, którzy tutaj są nowi, jeśli chodzi o programowanie funkcyjne, zachęcamy do zrobienia może jakiegoś krótkiego researchu właśnie o tych różnych elementach języków funkcyjnych, o których tutaj opowiedzieliśmy, i spróbujcie po prostu te koncepty gdzieś w swoim codziennym obiektowym życiu wprowadzać po prostu w kod, ta immutability czyste funkcje to naprawdę są rzeczy, które mogą ładnie uprościć kod i zwiększyć jego utrzymalność. Myślę, że to jest ciekawy też, jeśli chodzi o programowanie funkcyjne, kierunek rozwoju, gdzie tych ofert pracy może się pojawić w przyszłości więcej, a na pewno bardziej tutaj będzie brakować developerów niż w takiej Javie, tak mi się wydaje, bo po prostu tu większość ludzi wybiera jednak to, co jest najpopularniejsze, a są takie nisze, gdzie byście mogli po prostu spróbować je zagospodarować. Tak że zachęcamy. 

 

Tak, zachęcamy. Dzięki, Łukasz, za to podsumowanie, dzięki za rozmowę. Wszystkich z Was zapraszamy do wcześniejszych odcinków tej serii o software craftsmanship. Zapraszamy też do wcześniejszej serii, gdzie rozmawialiśmy z Łukaszem o narzędziach programistów. Zapraszamy do kolejnych odcinków, które już wkrótce. I oczywiście zapraszamy też na SolidJobs, gdzie możecie znaleźć ofertę pracy z widełkami wynagrodzeń. A jeśli w Waszym zespole jest taka potrzeba, to również można wystawić na SolidJobs ofertę pracy. 

Świetnie. To co, Łukasz? Słyszymy się już niedługo w następnym odcinku. Dzięki wielkie za dzisiaj. 

 

Dzięki, cześć. Do zobaczenia!

 

+ Pokaż całą transkrypcję
– Schowaj transkrypcję
mm
Krzysztof Kempiński
krzysztof@porozmawiajmyoit.pl

Jestem ekspertem w branży IT, w której działam od 2005 roku. Zawodowo zajmuję się web-developmentem i zarządzaniem działami IT. Dodatkowo prowadzę podcast, kanał na YouTube i blog programistyczny. Moją misją jest inspirowanie ludzi do poszerzania swoich horyzontów poprzez publikowanie wywiadów o trendach, technologiach i zjawiskach występujących w IT.