Jaka jest różnica między funkcjonalnymi i imperatywnymi językami programowania?

159

Większość języków głównego nurtu, w tym języki programowania obiektowego (OOP), takie jak C #, Visual Basic, C ++ i Java, zostały zaprojektowane przede wszystkim do obsługi programowania imperatywnego (proceduralnego), podczas gdy języki podobne do Haskell / gofer są czysto funkcjonalne. Czy ktoś może wyjaśnić, jaka jest różnica między tymi dwoma sposobami programowania?

Wiem, że wybór sposobu programowania zależy od wymagań użytkownika, ale dlaczego warto uczyć się języków programowania funkcjonalnego?

Swapnil Kotwal
źródło
1
sprawdź ten inny [post] [1]. Wyraźnie opisuje różnice. [1]: stackoverflow.com/questions/602444/ ...
theta

Odpowiedzi:

160

Definicja: Język imperatywny używa sekwencji instrukcji, aby określić, jak osiągnąć określony cel. Mówi się, że te instrukcje zmieniają stan programu, gdy każda z nich jest wykonywana po kolei.

Przykłady: Java jest językiem imperatywnym. Na przykład można utworzyć program, aby dodać serię liczb:

 int total = 0;
 int number1 = 5;
 int number2 = 10;
 int number3 = 15;
 total = number1 + number2 + number3; 

Każda instrukcja zmienia stan programu, od przypisywania wartości do każdej zmiennej do końcowego dodania tych wartości. Używając sekwencji pięciu instrukcji, program wyraźnie mówi, jak dodać do siebie liczby 5, 10 i 15.

Języki funkcyjne: Paradygmat programowania funkcjonalnego został wyraźnie stworzony w celu wspierania czysto funkcjonalnego podejścia do rozwiązywania problemów. Programowanie funkcyjne jest formą programowania deklaratywnego.

Zalety czystych funkcji: Głównym powodem implementacji transformacji funkcjonalnych jako czystych funkcji jest to, że czyste funkcje są komponowalne, to znaczy niezależne i bezpaństwowe. Cechy te niosą ze sobą szereg korzyści, w tym: Zwiększoną czytelność i łatwość konserwacji. Dzieje się tak, ponieważ każda funkcja jest zaprojektowana do wykonania określonego zadania, biorąc pod uwagę jej argumenty. Funkcja nie jest zależna od żadnego stanu zewnętrznego.

Łatwiejszy powtarzalny rozwój. Ponieważ kod jest łatwiejszy do refaktoryzacji, zmiany w projekcie są często łatwiejsze do wdrożenia. Na przykład załóżmy, że piszesz skomplikowaną transformację, a następnie zdajesz sobie sprawę, że jakiś kod jest powtarzany kilka razy w transformacji. Jeśli dokonasz refaktoryzacji za pomocą czystej metody, możesz dowolnie wywoływać swoją czystą metodę, nie martwiąc się o skutki uboczne.

Łatwiejsze testowanie i debugowanie. Ponieważ czyste funkcje można łatwiej testować w izolacji, można napisać kod testowy, który wywołuje czystą funkcję z typowymi wartościami, prawidłowymi przypadkami brzegowymi i nieprawidłowymi przypadkami brzegowymi.

Dla języków OOP lub imperatywnych:

Języki zorientowane obiektowo są dobre, gdy masz ustalony zestaw operacji na rzeczach, a gdy kod ewoluuje, dodajesz przede wszystkim nowe rzeczy. Można to osiągnąć poprzez dodanie nowych klas, które implementują istniejące metody, a istniejące klasy pozostają w spokoju.

Języki funkcjonalne są dobre, gdy masz ustalony zestaw rzeczy, a gdy twój kod ewoluuje, dodajesz głównie nowe operacje do istniejących rzeczy. Można to osiągnąć, dodając nowe funkcje, które obliczają z istniejącymi typami danych, a istniejące funkcje pozostają w spokoju.

Cons:

Wybór sposobu programowania zależy od wymagań użytkownika, więc szkoda jest tylko wtedy, gdy użytkownik nie wybierze właściwego sposobu.

Kiedy ewolucja idzie w złym kierunku, masz problemy:

  • Dodanie nowej operacji do programu obiektowego może wymagać edycji wielu definicji klas w celu dodania nowej metody
  • Dodanie nowego rodzaju rzeczy do programu funkcjonalnego może wymagać edycji wielu definicji funkcji w celu dodania nowego przypadku.
Chris
źródło
10
Czysta funkcja w tym przypadku jest odpowiednikiem funkcji matematycznej. Te same wejścia będą zawsze mapowane na te same wyjścia. Brakuje im również żadnych skutków ubocznych (innych niż zwracanie wartości lub wartości), co oznacza, że ​​kompilator może wykonać kilka fajnych optymalizacji i ułatwia równoległe uruchamianie funkcji, ponieważ nie ma z czym walczyć.
WorBlux,
Tak więc, właściwe sposoby i najlepsze praktyki tworzenia utrzymywalnych i testowalnych aplikacji OOP polegają na projektowaniu imperatywnego kodu z decleratywnym stanem umysłu?
Kemal Gültekin,
4
Nie widzę wyraźnej różnicy w tekście, w którym wyróżniona jest funkcja każdego programu. Większość opisu programowania proceduralnego można zamienić za pomocą imperatywnego tekstu programowania i odwrotnie.
AxeEffect
7
Ta odpowiedź ma na celu wyjaśnienie, czym jest programowanie funkcjonalne, ale nawet nie zadaje sobie trudu, aby zdefiniować czystą funkcję. Nie rozumiem, jak ktokolwiek mógłby przeczytać tę odpowiedź i odejść z poczuciem pewności, znając różnicę między programowaniem deklaratywnym a programowaniem proceduralnym.
Ringo
230

Oto różnica:

Tryb rozkazujący:

  • Początek
  • Włącz buty w rozmiarze 9 1/2.
  • Zrób miejsce w kieszeni na tablicę [7] kluczy.
  • Klucze schowaj w pomieszczeniu na klucze w kieszeni.
  • Wejdź do garażu.
  • Otwarty garaż.
  • Wprowadź samochód.

... i tak dalej i dalej ...

  • Wstaw mleko do lodówki.
  • Zatrzymać.

Deklaratywne, których funkcjonalność jest podkategorią:

  • Mleko to zdrowy napój, chyba że masz problemy z trawieniem laktozy.
  • Zwykle mleko przechowuje się w lodówce.
  • Lodówka to pudełko, w którym przechowywane rzeczy są chłodne.
  • Sklep to miejsce, w którym sprzedawane są przedmioty.
  • Przez „sprzedaż” rozumiemy wymianę rzeczy na pieniądze.
  • Również wymiana pieniędzy na rzeczy nazywana jest „kupowaniem”.

... i tak dalej i dalej ...

  • Upewnij się, że mamy mleko w lodówce (kiedy tego potrzebujemy - do leniwych języków funkcjonalnych).

Podsumowanie: W językach imperatywnych mówisz komputerowi, jak zmieniać bity, bajty i słowa w jego pamięci oraz w jakiej kolejności. W funkcjonalnych informujemy komputer, czym są rzeczy, działania itp. Na przykład mówimy, że silnia 0 to 1, a silnia każdej innej liczby naturalnej jest iloczynem tej liczby i silni jej poprzednika. Nie mówimy: aby obliczyć silnię n, zarezerwuj obszar pamięci i zapisz tam 1, a następnie pomnóż liczbę w tym obszarze przez liczby od 2 do n i zapisz wynik w tym samym miejscu, a na końcu, region pamięci będzie zawierał silnię.

Ingo
źródło
1
Dziękuję Ci. To świetny sposób, aby na to spojrzeć.
L-Samuels
5
Podobało mi się twoje wyjaśnienie @Igno, ale coś jest dla mnie niejasne. W deklaratywnym, nawet jeśli po prostu mówisz rzeczy, ale nadal musisz zmienić bity i dokonać zmian w stanach w maszynie, aby kontynuować. Wprawia mnie w zakłopotanie, że w jakiś sposób deklaratywne jest podobne do programowania proceduralnego (podobnie jak funkcje C) , a mimo to wewnętrznie istnieje między nimi duża różnica. Czy funkcje C nie są tym samym, co funkcje w programowaniu funkcyjnym (na poziomie maszyny)?
phoenisx
11
@Igno, podobnie jak Subroto, tak naprawdę nie rozumiem twojego wyjaśnienia. Wygląda na to, że to, co napisałeś, można podsumować następująco: Potrzebuję odpowiedzi ... uzyskaj odpowiedź. wydaje się, że ignoruje ważne fragmenty, czyli jak. Nie rozumiem, jak możesz po prostu ukryć tę część przed użytkownikiem, w pewnym momencie ktoś musi wiedzieć, jak to się stało ... nie możesz na zawsze trzymać czarodzieja za zasłoną.
Brett Thomas
3
Nie jest to zdalnie to, co rozumiem jako programowanie funkcjonalne. Myślałem, że programowanie funkcjonalne polega na usuwaniu ukrytych wejść i wyjść z funkcji.
Ringo
7
Zagmatwane wyjaśnienie.
JoeTidee
14

Większość współczesnych języków jest w różnym stopniu zarówno imperatywna, jak i funkcjonalna, ale aby lepiej zrozumieć programowanie funkcjonalne, najlepiej będzie wziąć przykład czystego języka funkcjonalnego, takiego jak Haskell, w przeciwieństwie do imperatywnego kodu w języku niezbyt funkcjonalnym, takim jak java / c #. Uważam, że zawsze łatwo jest to wyjaśnić na przykładzie, więc poniżej jest jeden.

Programowanie funkcjonalne: oblicz silnię z n ie n! tj. nx (n-1) x (n-2) x ... x 2 X 1

-- | Haskell comment goes like
-- | below 2 lines is code to calculate factorial and 3rd is it's execution  

factorial 0 = 1
factorial n = n * factorial (n - 1)
factorial 3

-- | for brevity let's call factorial as f; And x => y shows order execution left to right
-- | above executes as := f(3) as 3 x f(2) => f(2) as 2 x f(1) => f(1) as 1 x f(0) => f(0) as 1  
-- | 3 x (2 x (1 x (1)) = 6

Zauważ, że Haskel pozwala na przeciążanie funkcji do poziomu wartości argumentów. Poniżej znajduje się przykład kodu imperatywnego o rosnącym stopniu imperatywności:

//somewhat functional way
function factorial(n) {
  if(n < 1) {
     return 1;
  }
  return n * factorial(n-1);   
}
factorial(3);

//somewhat more imperative way
function imperativeFactor(n) {
  int f = 1
  for(int i = 1; i <= n; i++) {
     f = f * i
  }
  return f;
}

Ten odczyt może być dobrym odniesieniem do zrozumienia, jak imperatywny kod koncentruje się bardziej na tym, jak część, stan maszyny (pętla i in for), kolejność wykonywania, sterowanie przepływem.

Późniejszy przykład można postrzegać jako z grubsza kod java / c # lang, a pierwsza część jako ograniczenie samego języka w przeciwieństwie do Haskella do przeciążania funkcji wartością (zero), a zatem można powiedzieć, że nie jest to purystyczny język funkcjonalny, z drugiej strony ręka można powiedzieć, że obsługuje program funkcjonalny. do pewnego stopnia.

Ujawnienie: żaden z powyższych kodów nie jest testowany / wykonywany, ale mam nadzieję, że powinien być wystarczająco dobry, aby przekazać koncepcję; również byłbym wdzięczny za komentarze do każdej takiej korekty :)

stary mnich
źródło
1
Nie powinno być return n * factorial(n-1);?
jinawee,
@jinawee, dzięki za wskazanie, poprawiłem to odn * (n-1)
stary mnich
10

Programowanie funkcyjne jest formą programowania deklaratywnego, która opisuje logikę obliczeń, a kolejność wykonywania jest całkowicie pomniejszona.

Problem: Chcę zmienić to stworzenie z konia w żyrafę.

  • Wydłuż szyję
  • Wydłuż nogi
  • Zastosuj plamy
  • Daj stworzeniu czarny język
  • Usuń ogon konia

Każdy element można uruchomić w dowolnej kolejności, aby uzyskać ten sam wynik.

Programowanie imperatywne jest proceduralne. Stan i porządek są ważne.

Problem: Chcę zaparkować samochód.

  1. Zwróć uwagę na początkowy stan bramy garażowej
  2. Zatrzymaj samochód na podjeździe
  3. Jeśli brama garażowa jest zamknięta, otwórz bramę, pamiętaj o nowym stanie; w przeciwnym razie kontynuuj
  4. Wciągnij samochód do garażu
  5. Zamknij drzwi garażowe

Aby osiągnąć pożądany rezultat, należy wykonać każdy krok. Wciągnięcie do garażu, gdy drzwi garażowe są zamknięte, spowodowałoby pęknięcie bramy.

Jakub Keller
źródło
Widzę tylko różnicę między synchronizacją a synchronizacją.
Vladimir Vukanac,
@VladimirVukanac asynchroniczny / synchronizacja jest mechanizmem, a nie formą programowania
Jakub Keller
2
Och, dzięki, zbadam to więcej. Czy byłbyś tak uprzejmy, aby zaktualizować problem 1 tak, aby był taki sam jak problem 2 „Chcę zaparkować samochód”, ale napisany w funkcjonalny sposób? Wtedy równoległość zostanie wykluczona.
Vladimir Vukanac
6

Programowanie funkcjonalne to „programowanie z funkcjami”, w którym funkcja ma pewne oczekiwane właściwości matematyczne, w tym przezroczystość referencyjną. Z tych właściwości wypływają dalsze właściwości, w szczególności znane kroki rozumowania możliwe dzięki substytucyjności, które prowadzą do dowodów matematycznych (tj. Uzasadniania zaufania do wyniku).

Wynika z tego, że program funkcjonalny jest jedynie wyrażeniem.

Możesz łatwo zobaczyć kontrast między tymi dwoma stylami, odnotowując miejsca w programie imperatywnym, w których wyrażenie nie jest już referencyjnie przezroczyste (a zatem nie jest zbudowane z funkcji i wartości i nie może samo być częścią funkcji). Dwa najbardziej oczywiste miejsca to: mutacja (np. Zmienne) inne skutki uboczne nielokalny przepływ kontroli (np. Wyjątki)

Na tej strukturze programów jako wyrażeń, które składają się z funkcji i wartości, zbudowany jest cały praktyczny paradygmat języków, pojęć, „wzorców funkcjonalnych”, kombinatorów i różnych systemów typów i algorytmów oceny.

Zgodnie z najbardziej ekstremalną definicją, prawie każdy język - nawet C lub Java - można nazwać funkcjonalnym, ale zwykle ludzie rezerwują ten termin dla języków z konkretnie stosownymi abstrakcjami (takimi jak zamknięcia, niezmienne wartości i pomoce składniowe, takie jak dopasowywanie wzorców). Jeśli chodzi o użycie programowania funkcyjnego, to wymaga użycia functinów i buduje kod bez żadnych skutków ubocznych. używany do pisania dowodów

Romil Pawar
źródło
3

Imperatywny styl programowania był praktykowany w tworzeniu stron internetowych od 2005 do 2013 roku.

W przypadku programowania imperatywnego napisaliśmy kod, który dokładnie wyszczególniał, co powinna robić nasza aplikacja, krok po kroku.

Funkcjonalny styl programowania tworzy abstrakcję poprzez sprytne sposoby łączenia funkcji.

W odpowiedziach jest wzmianka o programowaniu deklaratywnym, aw związku z tym powiem, że programowanie deklaratywne wymienia pewne zasady, których mamy przestrzegać. Następnie podajemy do naszej aplikacji coś, co nazywamy stanem początkowym, i pozwalamy tym regułom definiować sposób działania aplikacji.

Teraz te krótkie opisy prawdopodobnie nie mają większego sensu, więc przejrzyjmy różnice między programowaniem imperatywnym i deklaratywnym, przechodząc przez analogię.

Wyobraź sobie, że nie tworzymy oprogramowania, ale zamiast tego zarabiamy na życie pieczeniem ciast. Być może jesteśmy złymi piekarzami i nie wiemy, jak upiec pyszne ciasto tak, jak powinniśmy.

Więc nasz szef daje nam listę kierunków, co znamy jako przepis.

Przepis podpowie nam, jak zrobić ciasto. Jeden przepis jest napisany imperatywnym stylem:

  1. Wymieszaj 1 szklankę mąki
  2. Dodaj 1 jajko
  3. Dodaj 1 szklankę cukru
  4. Wlej mieszaninę na patelnię
  5. Wstaw patelnię do piekarnika na 30 minut i 350 stopni F.

Deklaratywny przepis wykonałby następujące czynności:

1 szklanka mąki, 1 jajko, 1 szklanka cukru - stan początkowy

Zasady

  1. Jeśli wszystko jest wymieszane, umieść na patelni.
  2. Jeśli wszystko jest niezmieszane, włóż do miski.
  3. Jeśli wszystko jest na patelni, wstaw do piekarnika.

Tak więc podejścia imperatywne charakteryzują się podejściem krok po kroku. Rozpoczynasz od kroku pierwszego i przechodzisz do kroku 2 i tak dalej.

W końcu otrzymujesz produkt końcowy. Robiąc to ciasto, bierzemy te składniki, mieszamy je, wkładamy na patelnię i do piekarnika, a otrzymujemy produkt końcowy.

W świecie deklaratywnym jest inaczej. W recepturze deklaratywnej podzielilibyśmy nasz przepis na dwie oddzielne części, zaczynając od jednej części, która zawiera stan początkowy receptury, podobnie jak zmienne. Zatem naszymi zmiennymi są tutaj ilości naszych składników i ich rodzaj.

Bierzemy stan początkowy lub składniki początkowe i stosujemy do nich pewne zasady.

Więc bierzemy stan początkowy i powtarzamy te zasady w kółko, aż będziemy gotowi do spożycia rabarbarowego ciasta truskawkowego lub czegokolwiek innego.

Dlatego w podejściu deklaratywnym musimy wiedzieć, jak prawidłowo ustrukturyzować te reguły.

Więc zasady, które moglibyśmy chcieć zbadać nasze składniki lub stan, jeśli zostaną zmieszane, włóż je do garnka.

Z naszym stanem początkowym to nie pasuje, ponieważ nie wymieszaliśmy jeszcze naszych składników.

Tak więc zasada 2 mówi, że jeśli się nie zmieszają, wymieszaj je w misce. Okay yeah ta zasada obowiązuje.

Teraz mamy miskę mieszanych składników jako nasz stan.

Teraz ponownie stosujemy ten nowy stan do naszych zasad.

Więc zasada 1 mówi, że jeśli składniki są wymieszane, umieść je na patelni, ok, tak, teraz obowiązuje zasada 1, zróbmy to.

Teraz mamy ten nowy stan, w którym składniki są mieszane i na patelni. Zasada 1 nie ma już zastosowania, zasada 2 nie ma zastosowania.

Zasada 3 mówi, że jeśli składniki są na patelni, umieść je w piekarniku, świetnie, że zasada dotyczy tego nowego stanu, zróbmy to.

I kończymy na pysznej gorącej szarlotce lub czymkolwiek.

Teraz, jeśli jesteś podobny do mnie, możesz pomyśleć, dlaczego nadal nie robimy programowania imperatywnego. To ma sens.

Cóż, w przypadku prostych przepływów tak, ale większość aplikacji internetowych ma bardziej złożone przepływy, których nie można prawidłowo uchwycić za pomocą imperatywnego projektowania programowania.

W podejściu deklaratywnym możemy mieć pewne składniki początkowe lub stan początkowy, np. textInput=“”Pojedynczą zmienną.

Może tekst zaczyna się od pustego ciągu.

Bierzemy ten stan początkowy i stosujemy go do zestawu reguł zdefiniowanych w Twojej aplikacji.

  1. Jeśli użytkownik wprowadzi tekst, zaktualizuj wprowadzanie tekstu. Cóż, teraz to nie ma zastosowania.

  2. Jeśli szablon jest renderowany, oblicz widget.

  3. Jeśli textInput zostanie zaktualizowany, ponownie wyrenderuj szablon.

Cóż, nic z tego nie ma zastosowania, więc program po prostu zaczeka na jakieś wydarzenie.

Tak więc w pewnym momencie użytkownik aktualizuje wprowadzany tekst, a następnie możemy zastosować regułę numer 1.

Możemy to zaktualizować do “abcd”

Więc właśnie zaktualizowaliśmy nasze aktualizacje text i textInput, reguła numer 2 nie ma zastosowania, reguła numer 3 mówi, że jeśli wprowadzanie tekstu to aktualizacja, która właśnie nastąpiła, to ponownie renderuj szablon, a następnie wracamy do reguły 2, która mówi, że szablon jest renderowany , obliczyć widget, w porządku, obliczmy widget.

Ogólnie rzecz biorąc, jako programiści, chcemy dążyć do bardziej deklaratywnych projektów programistycznych.

Imperatyw wydaje się bardziej jasny i oczywisty, ale podejście deklaratywne daje się bardzo dobrze skalować w przypadku większych aplikacji.

Daniel
źródło
2

• Języki imperatywne:

  • Efektywne wykonanie

  • Złożona semantyka

  • Złożona składnia

  • Współbieżność jest zaprojektowana przez programistę

  • Złożone testy, bez referencyjnej przejrzystości, mają skutki uboczne

  • Ma stan

• Języki funkcjonalne:

  • Prosta semantyka

  • Prosta składnia

  • Mniej wydajne wykonanie

  • Programy mogą być automatycznie współbieżne

  • Proste testowanie, referencyjna przejrzystość, nie ma skutków ubocznych

  • Nie ma stanu
BoraKurucu
źródło
1

Myślę, że można wyrazić programowanie funkcjonalne w sposób imperatywny:

  • Korzystanie z wielu sprawdzeń stanu obiektów i if... else/ switchinstrukcji
  • Pewien mechanizm limitu czasu / oczekiwania, aby zająć się asynchronicznością

Z takim podejściem są ogromne problemy:

  • Zasady / procedury są powtarzane
  • Stanowość pozostawia szanse na skutki uboczne / błędy

Programowanie funkcjonalne, traktujące funkcje / metody jak obiekty i uwzględniające bezpaństwowość, narodziło się, aby rozwiązać te problemy, które moim zdaniem.

Przykładowe zastosowania: aplikacje frontendowe, takie jak Android, iOS lub logika aplikacji internetowych, w tym. komunikacja z backendem.

Inne wyzwania podczas symulacji programowania funkcjonalnego z kodem imperatywnym / proceduralnym:

  • Warunki wyścigu
  • Złożona kombinacja i sekwencja zdarzeń. Na przykład użytkownik próbuje wysłać pieniądze w aplikacji bankowej. Krok 1) Wykonaj wszystkie poniższe czynności równolegle, kontynuuj tylko wtedy, gdy wszystko jest w porządku a) Sprawdź, czy użytkownik jest nadal dobry (oszustwo, AML) b) sprawdź, czy użytkownik ma wystarczające saldo c) Sprawdź, czy odbiorca jest ważny i dobry (oszustwo, AML) itp. Krok 2) wykonaj operację transferu Krok 3) Pokaż aktualizację salda użytkownika i / lub jakiś rodzaj śledzenia. Na przykład z RxJava kod jest zwięzły i sensowny. Bez tego mogę sobie wyobrazić, że byłoby dużo kodu, niechlujnego i podatnego na błędy kodu

Wierzę również, że pod koniec dnia kod funkcjonalny zostanie przetłumaczony na kod asemblerowy lub maszynowy, co jest konieczne / proceduralne dla kompilatorów. Jednak o ile nie piszesz asemblera, ponieważ ludzie piszą kod w języku wysokiego poziomu / czytelnym dla człowieka, programowanie funkcjonalne jest bardziej odpowiednim sposobem wyrażenia dla wymienionych scenariuszy

ericn
źródło
-1

Wiem, że to pytanie jest starsze i inni już je dobrze wyjaśnili, chciałbym podać przykładowy problem, który wyjaśnia to samo w prostych słowach.

Problem: Zapisywanie tabeli jedynek.

Rozwiązanie: -

Według stylu rozkazującego: =>

    1*1=1
    1*2=2
    1*3=3
    .
    .
    .
    1*n=n 

Według stylu funkcjonalnego: =>

    1
    2
    3
    .
    .
    .
    n

Wyjaśnienie w stylu imperatywnym instrukcje piszemy bardziej jawnie i które można wywołać w bardziej uproszczony sposób.

W przypadku stylu funkcjonalnego rzeczy, które nie wymagają wyjaśnień, zostaną zignorowane.

Param NC
źródło