Niedawno uczyłem się programowania funkcjonalnego (szczególnie Haskell, ale przeszedłem też przez samouczki na temat Lisp i Erlang). Chociaż uważam te koncepcje za bardzo pouczające, nadal nie widzę praktycznej strony koncepcji „bez skutków ubocznych”. Jakie są praktyczne zalety tego rozwiązania? Próbuję myśleć funkcjonalnie, ale są sytuacje, które wydają się zbyt skomplikowane bez możliwości zapisania stanu w łatwy sposób (nie uważam monad Haskella za „łatwe”).
Czy warto kontynuować dogłębną naukę języka Haskell (lub innego języka czysto funkcjonalnego)? Czy programowanie funkcjonalne lub bezstanowe jest faktycznie bardziej produktywne niż proceduralne? Czy jest prawdopodobne, że będę dalej używać Haskell lub innego języka funkcjonalnego później, czy też powinienem się go uczyć tylko dla zrozumienia?
Mniej zależy mi na wydajności niż na produktywności. Więc głównie pytam, czy będę bardziej produktywny w języku funkcjonalnym niż w języku proceduralnym / obiektowym / czymkolwiek.
źródło
Im więcej elementów twojego programu jest bezpaństwowych, tym więcej jest sposobów na łączenie elementów bez zerwania . Siła paradygmatu bezpaństwowości nie polega na bezpaństwowości (lub czystości) per se , ale na zdolności, jaką daje on do pisania potężnych funkcji wielokrotnego użytku i łączenia ich.
Dobry samouczek z wieloma przykładami można znaleźć w artykule Johna Hughesa Why Functional Programming Matters (PDF).
Będziesz gęby bardziej produktywne, zwłaszcza jeśli wybrać język funkcjonalny, który posiada również algebraiczne typy danych i dopasowywanie wzorca (Caml, SML, Haskell).
źródło
Wiele innych odpowiedzi skupiało się na stronie wydajności (równoległości) programowania funkcjonalnego, co moim zdaniem jest bardzo ważne. Jednak zapytałeś konkretnie o produktywność, na przykład, czy możesz zaprogramować to samo szybciej w paradygmacie funkcjonalnym niż w imperatywnym.
Właściwie (z własnego doświadczenia) uważam, że programowanie w języku F # pasuje do sposobu, w jaki myślę, lepiej, więc jest łatwiej. Myślę, że to największa różnica. Programowałem zarówno w F #, jak i C #, aw F # jest dużo mniej „walki z językiem”, co uwielbiam. Nie musisz myśleć o szczegółach w F #. Oto kilka przykładów tego, co naprawdę mi się podoba.
Na przykład, mimo że F # jest wpisywany statycznie (wszystkie typy są rozwiązywane w czasie kompilacji), wnioskowanie o typie określa, jakie typy masz, więc nie musisz tego mówić. A jeśli nie może tego zrozumieć, automatycznie tworzy funkcję / klasę / cokolwiek ogólnego. Więc nigdy nie musisz pisać żadnych ogólnych, wszystko dzieje się automatycznie. Uważam, że oznacza to, że spędzam więcej czasu na myśleniu o problemie, a mniej na tym, jak go wdrożyć. W rzeczywistości za każdym razem, gdy wracam do C #, odkrywam, że naprawdę tęsknię za tego typu wnioskami, nigdy nie zdajesz sobie sprawy, jak rozpraszające to jest, dopóki nie musisz już tego robić.
Również w języku F #, zamiast pisać pętle, wywołujesz funkcje. To subtelna zmiana, ale znacząca, ponieważ nie musisz już myśleć o konstrukcji pętli. Na przykład, oto fragment kodu, który mógłby przejść i dopasować coś (nie pamiętam co, to z projektu układanki Euler):
Zdaję sobie sprawę, że zrobienie filtru, a potem mapy (czyli konwersji każdego elementu) w C # byłoby całkiem proste, ale trzeba myśleć na niższym poziomie. W szczególności musiałbyś napisać samą pętlę i mieć własną wyraźną instrukcję if i tego typu rzeczy. Od kiedy nauczyłem się F #, zdałem sobie sprawę, że łatwiej jest kodować w sposób funkcjonalny, gdzie jeśli chcesz filtrować, piszesz „filtr”, a jeśli chcesz mapować, piszesz „mapa”, zamiast implementować każdy szczegół.
Uwielbiam także operator |>, który moim zdaniem oddziela F # od ocaml i być może innych języków funkcjonalnych. Jest to operator potoku, który pozwala ci "potokować" wyjście jednego wyrażenia do wejścia innego wyrażenia. To sprawia, że kod podąża za tym, jak myślę. Podobnie jak w powyższym fragmencie kodu, to mówi: „weź sekwencję czynników, przefiltruj ją, a następnie zmapuj”. To bardzo wysoki poziom myślenia, którego nie opanujesz w imperatywnym języku programowania, ponieważ jesteś tak zajęty pisaniem pętli i instrukcji if. To jedyna rzecz, której najbardziej brakuje mi, gdy przechodzę na inny język.
Tak więc ogólnie, mimo że mogę programować zarówno w C #, jak i F #, łatwiej mi jest używać F #, ponieważ można myśleć na wyższym poziomie. Twierdzę, że ponieważ mniejsze szczegóły zostały usunięte z programowania funkcjonalnego (przynajmniej w F #), jestem bardziej produktywny.
Edycja : Widziałem w jednym z komentarzy, że prosiłeś o przykład „stanu” w funkcjonalnym języku programowania. F # można napisać imperatywnie, więc oto bezpośredni przykład tego, jak można mieć zmienny stan w F #:
źródło
a |> b (p1, p2)
to tylko cukier syntaktycznyb (a, p1, p2)
. Połącz to z prawidłowym skojarzeniem i masz to.Rozważ wszystkie trudne błędy, nad którymi debugowałeś przez długi czas.
Ile z tych błędów było spowodowanych „niezamierzonymi interakcjami” między dwoma oddzielnymi komponentami programu? (Prawie wszystkie błędy gwintowania mieć tego formularza: wyścigi obejmujące pisanie współdzielonych danych, zakleszczenia, ... Dodatkowo, powszechne jest znaleźć biblioteki, które mają jakiś nieoczekiwany wpływ na stan globalnej lub odczytu / zapisu rejestru / środowisko, etc.) I zakładałoby, że co najmniej 1 na 3 „twarde błędy” należy do tej kategorii.
Teraz, jeśli przełączysz się na programowanie bezstanowe / niezmienne / czyste, wszystkie te błędy znikną. Zamiast tego pojawiają się nowe wyzwania (np. Gdy chcesz , aby różne moduły wchodziły w interakcję ze środowiskiem), ale w języku takim jak Haskell te interakcje są jawnie reifikowane w systemie typów, co oznacza, że możesz po prostu spojrzeć na typ funkcja i powód dotyczący rodzaju interakcji, jakie może mieć z resztą programu.
To wielka wygrana z IMO „niezmienności”. W idealnym świecie wszyscy projektowalibyśmy wspaniałe interfejsy API, a nawet gdy rzeczy byłyby zmienne, efekty byłyby lokalne i dobrze udokumentowane, a „nieoczekiwane” interakcje byłyby ograniczone do minimum. W prawdziwym świecie istnieje wiele interfejsów API, które współdziałają ze stanem globalnym na niezliczone sposoby i są one źródłem najbardziej zgubnych błędów. Aspirowanie do bezpaństwowości jest dążeniem do pozbycia się niezamierzonych / niejawnych / zakulisowych interakcji między komponentami.
źródło
Jedną z zalet funkcji bezstanowych jest to, że umożliwiają wstępne obliczanie lub buforowanie wartości zwracanych przez funkcję. Nawet niektóre kompilatory C pozwalają na jawne oznaczanie funkcji jako bezstanowych, aby poprawić ich optymalizację. Jak wielu innych zauważyło, funkcje bezstanowe są znacznie łatwiejsze do zrównoleglenia.
Ale wydajność nie jest jedynym problemem. Czysta funkcja jest łatwiejsza do testowania i debugowania, ponieważ wszystko, co na nią wpływa, jest wyraźnie określone. A kiedy programuje się w języku funkcjonalnym, przyzwyczaić się do „zabrudzenia” jak najmniejszej liczby funkcji (z I / O itp.). Oddzielenie w ten sposób elementów stanowych jest dobrym sposobem na projektowanie programów, nawet w mniej funkcjonalnych językach.
„Uzyskanie” języków funkcjonalnych może zająć trochę czasu i trudno jest to wytłumaczyć komuś, kto nie przeszedł przez ten proces. Jednak większość ludzi, którzy wytrwali wystarczająco długo, w końcu zdaje sobie sprawę, że warto robić zamieszanie, nawet jeśli nie używają zbyt często języków funkcjonalnych.
źródło
sin(PI/3)
w swoim kodzie, gdzie PI jest stałą, kompilator może ocenić tę funkcję w czasie kompilacji i osadzić wynik w wygenerowanym kodzie.Bez stanu bardzo łatwo jest automatycznie zrównoleglać kod (ponieważ procesory są wykonane z coraz większą liczbą rdzeni, jest to bardzo ważne).
źródło
Bezstanowe aplikacje internetowe są niezbędne, gdy zaczynasz mieć większy ruch.
Może istnieć wiele danych użytkownika, których nie chcesz przechowywać po stronie klienta, na przykład ze względów bezpieczeństwa. W takim przypadku musisz przechowywać go po stronie serwera. Możesz użyć domyślnej sesji aplikacji internetowych, ale jeśli masz więcej niż jedną instancję aplikacji, musisz upewnić się, że każdy użytkownik jest zawsze kierowany do tej samej instancji.
Systemy równoważenia obciążenia często mają możliwość tworzenia `` lepkich sesji '', podczas których system równoważenia obciążenia wie, do którego serwera wysłać żądanie użytkownika. Nie jest to jednak idealne rozwiązanie, na przykład oznacza, że za każdym razem, gdy ponownie uruchomisz aplikację internetową, wszyscy podłączeni użytkownicy stracą sesję.
Lepszym podejściem jest przechowywanie sesji za serwerami sieciowymi w jakimś magazynie danych, obecnie dostępnych jest wiele świetnych produktów nosql (redis, mongo, flexiblesearch, memcached). W ten sposób serwery internetowe są bezstanowe, ale nadal masz stan po stronie serwera, a dostępnością tego stanu można zarządzać, wybierając odpowiednią konfigurację magazynu danych. Te magazyny danych zwykle mają dużą nadmiarowość, więc prawie zawsze powinno być możliwe wprowadzanie zmian w aplikacji internetowej, a nawet w magazynie danych, bez wpływu na użytkowników.
źródło
Niedawno napisałem post na ten temat: On The Importance of Purity .
źródło