No na razie mamy:
- niemodyfikowalność
- czyste funkcje
- thread safe
Ogólnie trudno jest to sprzedać, chyba, że większa część aplikacji to obliczenia.
mam pewien przykład na porównanie programowania funkcyjnego i imperatywnego, chociaż opis będzie trochę rozwlekły:
ostatnio robiłem poprawki do parsowania xmli w kilku apkach, w każdej było trochę inaczej, ale były dwa podejścia:
- wyciąganie pól xpathem (albo czymś a'la xpath), operowanie na niemutowalnym drzewie dom
- parsowanie oparte o parser sax https://en.wikipedia.org/wiki/Simple_API_for_XML gdzie trzeba samemu przetrzymywać stan w zmiennych mutowalnych i na ich podstawie decydować co zrobić z aktualnie przetwarzanym (tzn. zawartym w evencie z sax parsera) kawałkiem xmla
podejście z saxem jest strasznie zamotane, bo ifologia nie dość, że jest nietrywialna sama w sobie, to jeszcze trzeba wyobrazić sobie jak będzie działać, gdy sax parser będzie odpalał poszczególne eventy. eventy są różne, a w każdym jest ledwo kawałek xmla (ten aktualnie przetwarzany) podany i trzeba odpowiednio zareagować.
imperatywne podejście z parserem sax jest niby lżejsze, bo nie trzeba ładować całego ciężkiego drzewa dom do pamięci, ale za to podejście funkcyjnego z niemutowalnym drzewem dom jest znacznie prostsze do prześledzenia i zrozumienia.
podobna różnica była z przerabianiem xmli, np. rozdzielaniem ich, za pomocą lekkich imperatywnych api ze strumieniami / eventami czy ciężkich funkcyjnych z drzewem dom. podobnie jak wcześniej, w imperatywnych rozwiązaniach trzeba mieć szereg mutowalnych zmiennych na boku i robić na nich ifologię.
w pozostałych zastosowaniach (poza parsowaniem xmli) sprawa jest podobna. imperatywny kod to często ifologia zależąca od mutowalnych zmiennych, których zmiany nieraz trudno prześledzić (albo łatwo coś przegapić w gąszczu trudnych do skoordynowania zmian). w programowaniu funkcyjnym mamy niemutowalne zmienne i struktury danych (pomijam monady io, io ref, itp, skupiam się tylko na kodzie bez tzn. efektów, de facto ubocznych jeśli zostaną wykonane), więc wiadomo co skąd się bierze. przekazując niemutowalne dane do funkcji mam pewność, że nie zostaną zmodyfikowane. natomiast w imperatywce dane mogą być modyfikowane w dowolnym momencie i trzeba posiłkować się (jawnymi czy niejawnymi) kontraktami (definiującymi zachowanie kodu), żeby wiedzieć czy dane wywołanie może namieszać w poprzednio skonstruowanych danych.
niemutowalność i brak innych imperatywnych efektów ubocznych (np. rzucania wyjątków) oznacza, że można w miarę bezpiecznie refaktorować kod. problemem jest tylko wydajność - liczenie czegoś niepotrzebnie oczywiście spowalnia program. natomiast jeśli chodzi o deduplikację czy ściśle przenoszenie kawałków kodu (bez przerabiania go) to w programowaniu funkcyjnym jest bezpiecznie. w imperatywce może być tak, że funkcja pobierająca dane i zwracająca dane może przy okazji coś mutować i wtedy jest różnica czy wywołamy ją raz czy inną liczbę razy, nawet z tymi samymi parametrami - stąd usuwanie deduplikacji wymaga najpierw upewnienia się, że nie zmieniamy liczby wystąpień efektów ubocznych. przenoszenie kodu z miejsca w miejsce też może spowodować problem jeśli ten kod rzuca wyjątkami, a my je łapiemy w jednym miejscu, a w innym nie (albo reagujemy na te same wyjątki inaczej).
na wysokim poziomie podoba mi się też zarządanie efektami w programowaniu funkcyjnym (tymi de facto na samym końcu ubocznymi, ale odroczonymi, bo opakowanymi w monadę io), np. używając https://zio.dev/ (biblioteka jest w zasadzie głównie dla osób chociażby w miarę dobrze znających scalę). operowanie na monadkach io, mając już z nimi doświadczenie, jest wygodniejsze niż imperatywka, bo monady io są znacznie bardziej przewidywalne. łatwiej kontrolować zrównoleglenie, łapanie wyjątków, ponowienia itp itd niż w innych podejściach. tu musiałbym całą sprawę rozrysować (np. zrobić jakieś slajdy i omówienie do nich), żeby to było przekonujące, więc na razie brzmi dość enigmatycznie dla osób nieznających 'czysto' funkcyjnego podejścia.