Naprawdę trudno mi zrozumieć różnicę między paradygmatami programowania proceduralnego i funkcjonalnego .
Oto pierwsze dwa akapity z wpisu Wikipedii na temat programowania funkcjonalnego :
W informatyce programowanie funkcjonalne jest paradygmatem programowania, który traktuje obliczenia jako ocenę funkcji matematycznych i unika danych stanu i zmiennych. Podkreśla zastosowanie funkcji, w przeciwieństwie do imperatywnego stylu programowania, który kładzie nacisk na zmiany stanu. Programowanie funkcjonalne ma swoje korzenie w rachunku lambda, formalnym systemie opracowanym w latach trzydziestych XX wieku w celu badania definicji funkcji, zastosowania funkcji i rekurencji. Wiele funkcjonalnych języków programowania można traktować jako rozwinięcia rachunku lambda.
W praktyce różnica między funkcją matematyczną a pojęciem „funkcji” używanym w programowaniu imperatywnym polega na tym, że funkcje imperatywne mogą mieć skutki uboczne, zmieniając wartość stanu programu. Z tego powodu brakuje im referencyjnej przejrzystości, tzn. To samo wyrażenie językowe może skutkować różnymi wartościami w różnym czasie, w zależności od stanu wykonywanego programu. I odwrotnie, w kodzie funkcyjnym wartość wyjściowa funkcji zależy tylko od argumentów wprowadzonych do funkcji, więc
f
dwukrotne wywołanie funkcji z tą samą wartością argumentux
da ten sam wynikf(x)
oba razy. Eliminacja skutków ubocznych może znacznie ułatwić zrozumienie i przewidywanie zachowania programu, co jest jedną z kluczowych motywacji dla rozwoju programowania funkcjonalnego.
W ust. 2, gdzie jest napisane
I odwrotnie, w kodzie funkcyjnym wartość wyjściowa funkcji zależy tylko od argumentów wprowadzanych do funkcji, więc
f
dwukrotne wywołanie funkcji z tą samą wartością argumentux
da za każdym razem ten sam wynikf(x)
.
Czy to nie jest dokładnie ten sam przypadek w przypadku programowania proceduralnego?
Czego należy szukać w wyróżniających się procedurach i funkcjach?
Odpowiedzi:
Programowanie funkcjonalne
Programowanie funkcjonalne odnosi się do zdolności do traktowania funkcji jako wartości.
Rozważmy analogię do „zwykłych” wartości. Możemy wziąć dwie liczby całkowite i połączyć je za pomocą
+
operatora, aby otrzymać nową liczbę całkowitą. Lub możemy pomnożyć liczbę całkowitą przez liczbę zmiennoprzecinkową, aby otrzymać liczbę zmiennoprzecinkową.W programowaniu funkcjonalnym możemy połączyć dwie wartości funkcji, aby utworzyć nową wartość funkcji za pomocą operatorów takich jak compose lub lift . Lub możemy połączyć wartość funkcji i wartość danych, aby utworzyć nową wartość danych za pomocą operatorów takich jak map lub fold .
Należy zauważyć, że wiele języków ma możliwości programowania funkcjonalnego - nawet języki, które zwykle nie są uważane za języki funkcjonalne. Nawet dziadek FORTRAN obsługiwał wartości funkcji, chociaż nie oferował zbyt wiele w zakresie operatorów łączących funkcje. Aby można było nazwać język „funkcjonalnym”, musi on w dużym stopniu obejmować możliwości programowania funkcjonalnego.
Programowanie proceduralne
Programowanie proceduralne odnosi się do zdolności do hermetyzacji wspólnej sekwencji instrukcji w procedurze, tak aby te instrukcje mogły być wywoływane z wielu miejsc bez uciekania się do kopiowania i wklejania. Ponieważ procedury były bardzo wczesnym rozwojem w programowaniu, możliwości są prawie zawsze związane ze stylem programowania wymaganym przez programowanie maszynowe lub w języku asemblerowym: styl, który podkreśla pojęcie lokalizacji pamięci i instrukcji, które przenoszą dane między tymi lokalizacjami.
Kontrast
Te dwa style nie są tak naprawdę przeciwieństwami - po prostu różnią się od siebie. Istnieją języki, które w pełni obejmują oba style (na przykład LISP). Poniższy scenariusz może dać poczucie pewnych różnic w obu stylach. Napiszmy kod dla nonsensownego wymagania, w którym chcemy określić, czy wszystkie słowa na liście mają nieparzystą liczbę znaków. Po pierwsze, styl proceduralny:
Przyjmę za pewnik, że ten przykład jest zrozumiały. Teraz funkcjonalny styl:
Działając od wewnątrz, ta definicja spełnia następujące funkcje:
compose(odd, length)
łączy funkcjeodd
i wlength
celu utworzenia nowej funkcji, która określa, czy długość łańcucha jest nieparzysta.map(..., words)
wywołuje tę nową funkcję dla każdego elementu wwords
, ostatecznie zwracając nową listę wartości logicznych, z których każda wskazuje, czy odpowiednie słowo ma nieparzystą liczbę znaków.apply(and, ...)
stosuje operator "and" do otrzymanej listy i -ing wszystkich logicznych razem, aby uzyskać ostateczny wynik.Na podstawie tych przykładów widać, że programowanie proceduralne jest bardzo zainteresowane przenoszeniem wartości w zmiennych i jawnym opisywaniem operacji potrzebnych do uzyskania końcowego wyniku. W przeciwieństwie do tego styl funkcjonalny kładzie nacisk na kombinację funkcji wymaganych do przekształcenia początkowego wkładu w ostateczny wynik.
Przykład pokazuje również typowe względne rozmiary kodu proceduralnego i funkcjonalnego. Ponadto pokazuje, że parametry wydajnościowe kodu proceduralnego mogą być łatwiejsze do zobaczenia niż w przypadku kodu funkcjonalnego. Zastanów się: czy funkcje obliczają długości wszystkich słów na liście, czy też każde zatrzymuje się natychmiast po znalezieniu pierwszego słowa o parzystej długości? Z drugiej strony kod funkcjonalny pozwala wysokiej jakości implementacji na wykonanie dość poważnej optymalizacji, ponieważ przede wszystkim wyraża zamiar, a nie jawny algorytm.
Dalsze czytanie
To pytanie pojawia się często ... patrz na przykład:
Wykład z nagrodą Johna Backusa Turinga szczegółowo omawia motywy programowania funkcjonalnego:
Czy programowanie można uwolnić od stylu von Neumanna?
Naprawdę nie powinienem wspominać o tym artykule w obecnym kontekście, ponieważ staje się dość techniczny, dość szybko. Po prostu nie mogłem się oprzeć, ponieważ uważam, że jest to naprawdę fundamentalne.
Dodatek - 2013
Komentatorzy zwracają uwagę, że popularne współczesne języki oferują inne style programowania, wykraczające poza proceduralne i funkcjonalne. Takie języki często oferują jeden lub więcej z następujących stylów programowania:
Zobacz komentarze poniżej, aby zobaczyć przykłady tego, jak przykłady pseudokodów w tej odpowiedzi mogą skorzystać z niektórych udogodnień dostępnych w tych innych stylach. W szczególności przykład proceduralny odniesie korzyści z zastosowania praktycznie dowolnej konstrukcji wyższego poziomu.
Przedstawione przykłady celowo unikają mieszania się z innymi stylami programowania, aby podkreślić różnicę między dwoma omawianymi stylami.
źródło
odd_words(words)
definicja robi coś innego niż odpowiedźallOdd
. Do filtrowania i mapowania często preferowane są wyrażenia listowe, ale tutaj funkcjaallOdd
ma zredukować listę słów do pojedynczej wartości logicznej.Prawdziwą różnicą między programowaniem funkcjonalnym i imperatywnym jest sposób myślenia - programiści imperatywni myślą o zmiennych i blokach pamięci, podczas gdy programiści funkcjonalni myślą: „Jak mogę przekształcić moje dane wejściowe w moje dane wyjściowe” - Twój „program” jest potokiem i zestaw przekształceń danych, aby pobrać je z wejścia do wyjścia. To interesująca część IMO, a nie bit „Nie powinieneś używać zmiennych”.
W konsekwencji tego nastawienia programy FP zazwyczaj opisują, co się stanie, zamiast konkretnego mechanizmu tego, jak to się stanie - jest to potężne, ponieważ jeśli możemy jasno określić, co oznacza „Wybierz”, „Gdzie” i „Agreguj”, mogą swobodnie wymieniać swoje implementacje, tak jak robimy to z AsParallel () i nagle nasza aplikacja jednowątkowa skaluje się do n rdzeni.
źródło
Nie, ponieważ kod proceduralny może mieć skutki uboczne. Na przykład może przechowywać stan między połączeniami.
To powiedziawszy, możliwe jest napisanie kodu spełniającego to ograniczenie w językach uważanych za proceduralne. W niektórych językach uważanych za funkcjonalne możliwe jest również napisanie kodu, który łamie to ograniczenie.
źródło
Nie zgadzam się z odpowiedzią WReach. Zdekonstruujmy nieco jego odpowiedź, aby zobaczyć, skąd bierze się spór.
Najpierw jego kod:
i
Pierwszą rzeczą, na którą należy zwrócić uwagę, jest to, że łączy:
programowania i brakuje możliwości programowania w stylu iteracyjnym, aby mieć bardziej wyraźny przepływ sterowania niż typowy styl funkcjonalny.
Porozmawiajmy szybko o tym.
Styl skoncentrowany na ekspresji to taki, w którym rzeczy w jak największym stopniu oceniają rzeczy. Chociaż języki funkcjonalne słyną z zamiłowania do wyrażeń, w rzeczywistości możliwe jest posiadanie języka funkcjonalnego bez wyrażeń, które można komponować. Zamierzam wymyślić jedną, w której nie ma wyrażeń, są tylko stwierdzenia.
Jest to prawie to samo, co podano wcześniej, z wyjątkiem tego, że funkcje są powiązane wyłącznie za pomocą łańcuchów instrukcji i powiązań.
Styl programowania zorientowany na iterator może być wykorzystany przez Pythona. Użyjmy czysto iteracyjnego stylu opartego na iteratorach:
Nie jest to funkcjonalne, ponieważ każda klauzula jest procesem iteracyjnym i są one powiązane ze sobą jawną pauzą i wznowieniem ramek stosu. Składnia może być inspirowana częściowo językiem funkcjonalnym, ale jest stosowana do jego całkowicie iteracyjnego wykonania.
Oczywiście możesz to skompresować:
Imperatyw nie wygląda teraz tak źle, co? :)
Ostatni punkt dotyczył bardziej wyraźnego przepływu kontroli. Przepiszmy oryginalny kod, aby wykorzystać to:
Używając iteratorów, możesz mieć:
Jaki jest więc sens języka funkcjonalnego, jeśli różnica jest między:
Główną ostateczną cechą funkcjonalnego języka programowania jest to, że usuwa on mutacje jako część typowego modelu programowania. Ludzie często rozumieją, że oznacza to, że funkcjonalny język programowania nie ma instrukcji lub używa wyrażeń, ale są to uproszczenia. Język funkcyjny zastępuje jawne obliczenia deklaracją zachowania, na podstawie której język dokonuje redukcji.
Ograniczenie się do tego podzbioru funkcji pozwala mieć więcej gwarancji dotyczących zachowania programów, a to pozwala na swobodne ich komponowanie.
Jeśli masz język funkcjonalny, tworzenie nowych funkcji jest zwykle tak proste, jak tworzenie blisko powiązanych funkcji.
Nie jest to proste, a może nawet niemożliwe, jeśli nie masz jawnie kontrolowanych globalnych zależności funkcji. Najlepszą cechą programowania funkcjonalnego jest to, że można konsekwentnie tworzyć bardziej ogólne abstrakcje i mieć pewność, że można je połączyć w większą całość.
źródło
apply
że operacja nie jest tym samym co operacjafold
lubreduce
, chociaż zgadzam się z niezłą możliwością posiadania bardzo ogólnych algorytmów.apply
znaczeniufold
lubreduce
, ale wydaje mi się, że w tym kontekście musi to być, aby zwracało wartość logiczną.W paradygmacie proceduralnym (czy zamiast tego powinienem powiedzieć „programowanie strukturalne”?), Masz współdzieloną zmienną pamięć i instrukcje, które odczytują / zapisują ją w określonej kolejności (jedna po drugiej).
W paradygmacie funkcjonalnym masz zmienne i funkcje (w sensie matematycznym: zmienne nie zmieniają się w czasie, funkcje mogą tylko obliczać coś na podstawie swoich danych wejściowych).
(Jest to nadmiernie uproszczone, np. FPL zazwyczaj mają ułatwienia do pracy z pamięcią zmienną, podczas gdy języki proceduralne mogą często obsługiwać procedury wyższego rzędu, więc rzeczy nie są tak jasne; ale to powinno dać ci pomysł)
źródło
Charming Python: Programowanie funkcyjne w Pythonie z IBM Developerworks naprawdę pomógł mi zrozumieć różnicę.
Zwłaszcza dla kogoś, kto trochę zna Pythona, przykłady kodu w tym artykule, w których robienie różnych rzeczy funkcjonalnie i proceduralnie są skontrastowane, mogą wyjaśnić różnicę między programowaniem proceduralnym i funkcjonalnym.
źródło
W programowaniu funkcjonalnym, aby zrozumieć znaczenie symbolu (nazwa zmiennej lub funkcji), tak naprawdę wystarczy znać 2 rzeczy - aktualny zakres i nazwę symbolu. Jeśli masz czysto funkcjonalny język z niezmiennością, oba te pojęcia są „statyczne” (przepraszam za bardzo przeciążoną nazwę), co oznacza, że możesz zobaczyć zarówno bieżący zakres, jak i nazwę - po prostu patrząc na kod źródłowy.
W programowaniu proceduralnym, jeśli chcesz odpowiedzieć na pytanie, jaka jest wartość za
x
tobą, musisz również wiedzieć, jak się tam dostałeś, sam zakres i nazwa nie wystarczą. I właśnie to uznałbym za największe wyzwanie, ponieważ ta ścieżka wykonania jest właściwością „runtime” i może zależeć od tak wielu różnych rzeczy, że większość ludzi uczy się po prostu debugować, a nie próbować odzyskać ścieżkę wykonania.źródło
Niedawno zastanawiałem się nad różnicą związaną z problemem wyrażenia . Opis Phila Wadlera jest często cytowany, ale zaakceptowana odpowiedź na to pytanie jest prawdopodobnie łatwiejsza do zrozumienia. Zasadniczo wydaje się, że języki imperatywne wybierają jedno podejście do problemu, podczas gdy języki funkcjonalne wybierają drugie.
źródło
Jedną wyraźną różnicą między tymi dwoma paradygmatami programowania jest stan.
W programowaniu funkcyjnym stan jest unika. Mówiąc najprościej, nie będzie zmiennej, której zostanie przypisana wartość.
Przykład:
Jednak programowanie proceduralne używa stanu.
Przykład:
źródło