Jest tu kilka świetnych odpowiedzi. Interesującą rzeczą jest nie w pełni wyjaśniony, że deklaratywne i konieczne są komplementarne i symbiotyczne, nie tylko różne style lub co vs. sposób .
Zestaw
1
@Kit Imo, niektóre odpowiedzi na tej stronie łączą warunki. DP == przezroczystość referencyjna (RT). DP i IP to przeciwieństwa, dlatego afaika nie jest uzupełnieniem całości, tzn. Cały program można napisać w dowolnym stylu. Wywołanie funkcji może być DP (RT) lub IP, jej implementacja może być albo mieszana. Nie są symbiotyczne w tym sensie, że wywołanie funkcji IP w funkcji DP w przeciwnym razie może spowodować wywołanie funkcji DP przez IP. Są symbiotyczne w tym sensie, że programy ze świata rzeczywistego (np. Reaktywne funkcjonalnie) mogą wykorzystywać różne połączenia, np. Wywołania najwyższego poziomu IP w funkcjach DP.
W chwili pisania tego tekstu najlepiej głosowane odpowiedzi na tej stronie są nieprecyzyjne i niejasne w definicji deklaratywnej vs. imperatywnej, w tym odpowiedzi, która cytuje Wikipedię. Niektóre odpowiedzi łączą warunki na różne sposoby.
Zobacz także moje wyjaśnienie, dlaczego programowanie w arkuszach kalkulacyjnych jest deklaratywne, niezależnie od tego, że formuły mutują komórki.
Ponadto kilka odpowiedzi twierdzi, że programowanie funkcjonalne musi być podzbiorem deklaratywnym. Od tego momentu zależy, czy odróżnimy „funkcję” od „procedury”. Najpierw zajmijmy się trybem rozkazującym vs. deklaratywnym.
Definicja wyrażenia deklaratywnego
Tylko atrybut, który może ewentualnie odróżnić deklaratywny wyraz z bezwzględnej wyrażenia jest referential przezroczystość (RT) podokna wyrażeń. Wszystkie pozostałe atrybuty są albo wspólne dla obu typów wyrażeń, albo pochodzą z RT.
Język w 100% deklaratywny (tj. Taki, w którym każde możliwe wyrażenie to RT) nie pozwala (wśród innych wymagań RT) na mutację przechowywanych wartości, np. HTML i większość Haskell.
Definicja ekspresji RT
RT jest często określane jako „bez skutków ubocznych”. Pojęcie efektów nie ma precyzyjnej definicji, więc niektórzy ludzie nie zgadzają się, że „brak efektów ubocznych” jest taki sam jak RT. RT ma precyzyjną definicję .
Ponieważ każde podwyrażenie jest koncepcyjnie wywołaniem funkcji, RT wymaga, aby implementacja funkcji (tj. Wyrażenie (-a) wewnątrz wywoływanej funkcji) nie mogła uzyskać dostępu do stanu zmiennego zewnętrznego względem funkcji (dostęp do zmiennego stanu lokalnego jest dozwolony). Mówiąc wprost, funkcja (implementacja) powinna być czysta .
Definicja funkcji czystej
Często mówi się, że czysta funkcja „nie ma skutków ubocznych”. Pojęcie efektów nie ma precyzyjnej definicji, więc niektórzy ludzie się z tym nie zgadzają.
Funkcje Pure mają następujące atrybuty.
jedynym obserwowalnym wyjściem jest wartość zwracana.
jedyną zależnością wyjściową są argumenty.
argumenty są w pełni określone przed wygenerowaniem jakiegokolwiek wyniku.
Pamiętaj, że RT dotyczy wyrażeń (które obejmują wywołania funkcji), a czystość dotyczy (implementacji) funkcji.
Niejasnym przykładem nieczystych funkcji tworzących wyrażenia RT jest współbieżność, ale dzieje się tak, ponieważ czystość jest zepsuta na warstwie abstrakcji przerwań. Naprawdę nie musisz tego wiedzieć. Aby tworzyć wyrażenia RT, wywołujesz funkcje czyste.
Pochodne atrybuty RT
Wszelkie inne atrybuty cytowane w programowaniu deklaratywnym, np. Cytat z 1999 r. Używany przez Wikipedię, albo pochodzą z RT, albo są wspólne z programowaniem imperatywnym. To dowodzi, że moja precyzyjna definicja jest poprawna.
Języki deklaratywne nie mają struktur kontroli pętli, np. forI whileponieważ z powodu niezmienności warunek pętli nigdy się nie zmieni.
Języki deklaratywne nie wyrażają przepływu sterującego innego niż kolejność zagnieżdżonych funkcji (inaczej zależności logiczne), ponieważ z powodu niezmienności inne wybory kolejności oceny nie zmieniają wyniku (patrz poniżej).
Języki deklaratywne wyrażają logiczne „kroki” (tj. Zagnieżdżoną kolejność wywołań funkcji RT), ale to, czy każde wywołanie funkcji jest semantyczne wyższego poziomu (tj. „Co robić”), nie jest wymogiem programowania deklaratywnego. Różnica od imperatywu polega na tym, że ze względu na niezmienność (tj. Bardziej ogólnie RT), te „kroki” nie mogą zależeć od stanu zmiennego, a jedynie od relacyjnej kolejności wyrażonej logiki (tj. Kolejności zagnieżdżania wywołań funkcji, czyli podwyrażeń ).
Na przykład akapit HTML <p>nie może zostać wyświetlony, dopóki nie zostaną ocenione wyrażenia podrzędne (tj. Znaczniki) w akapicie. Nie ma stanu zmiennego, tylko zależność od kolejności wynikająca z logicznej relacji hierarchii znaczników (zagnieżdżanie podwyrażeń, które są analogicznie zagnieżdżonymi wywołaniami funkcji ).
Zatem istnieje atrybut pochodną niezmienność (ogólnie RT), to wyrażenia deklaratywne wyrażają tylko na logiczne związki z części składowych (tj argumentów Podfunkcja ekspresyjnych), a nie zmienne stanu związków.
Zamówienie oceny
Wybór kolejności oceny podwyrażeń może dawać różne wyniki tylko wtedy, gdy którekolwiek z wywołań funkcji nie jest RT (tj. Funkcja nie jest czysta), np. W obrębie funkcji można uzyskać dostęp do pewnego stanu zmiennego zewnętrznego względem funkcji.
Na przykład, biorąc pod uwagę kilka zagnieżdżonych wyrażeń, np f( g(a, b), h(c, d) ), chętny i leniwy ocenę argumentów funkcji dadzą takie same wyniki, jeśli te funkcje f, gi hsą czyste.
Natomiast jeśli funkcje f, gi hnie są czyste, to wybór kolejności oceny może dać inny wynik.
Uwaga: wyrażenia zagnieżdżone są funkcjami zagnieżdżonymi koncepcyjnie, ponieważ operatory wyrażeń są po prostu wywołaniami funkcji udającymi jednolity przedrostek, jednolity postfiks lub zapis binarny.
Stycznie, jeśli wszystkie identyfikatory, na przykład a, b, c, dsą niezmienne wszędzie stan zewnętrznego programu nie są dostępne (tj I / O) i nie dochodzi do pęknięcia warstwy abstrakcji, wtedy funkcje są zawsze czyste.
Nawiasem mówiąc, Haskell ma inną składnię f (g a b) (h c d).
Szczegóły zamówienia ewaluacyjnego
Funkcja jest przejściem stanu (nie zmienną zapamiętaną wartością) z wejścia na wyjście. W przypadku kompozycji RT wywołań funkcji czystych kolejność wykonywania tych przejść stanu jest niezależna. Przejście stanu każdego wywołania funkcji jest niezależne od innych, ze względu na brak skutków ubocznych i zasadę, że funkcję RT można zastąpić jej wartością buforowaną . Aby skorygować popularne nieporozumienie , czysta kompozycja monadyczna jest zawsze deklaratywna i RT , pomimo faktu, że IOmonada Haskella jest prawdopodobnie nieczysta, a zatem bezwzględnie konieczne jest, aby Worldstan był zewnętrzny względem programu (ale w sensie poniższego zastrzeżenia, skutki uboczne są izolowane).
Szybka ocena oznacza, że argumenty funkcji są oceniane przed wywołaniem funkcji, a leniwa ocena oznacza, że argumenty nie są oceniane, dopóki (i jeśli) są dostępne w obrębie funkcji.
Definicja : parametry funkcji są deklarowane w miejscu definicji funkcji , a argumenty funkcji są podawane w miejscu wywołania funkcji . Poznaj różnicę między parametrem a argumentem .
Koncepcyjnie, wszystkie wyrażenia są (kompozycja) wywołania funkcji, np stałe są funkcje bez nakładów, operatory jednoargumentowe są funkcje z jednego wejścia, dwuargumentowe operatory są funkcje z dwoma wejściami, konstruktorzy są funkcjami, oraz stwierdzenia nawet kontrolne (np if, for, while) można modelować za pomocą funkcji. Rozkaz, że te argumentów funkcji (nie mylić z zagnieżdżonych funkcji celu wywołania) są oceniane nie deklaruje składni, np f( g() )mógłby chętnie oceniają gnastępnie fna g„s wyniku czy może to ocenić fi ocenić tylko leniwie g, gdy jego wynik jest potrzebny wewnątrz f.
Zastrzeżenie, brak pełnego języka Turinga (tzn. Który umożliwia nieograniczoną rekurencję) jest całkowicie deklaratywny, np. Leniwa ocena wprowadza nieokreśloność pamięci i czasu. Ale te skutki uboczne wynikające z wyboru kolejności oceny są ograniczone do zużycia pamięci, czasu wykonania, opóźnienia, braku terminacji i zewnętrznej histerezy , a zatem zewnętrznej synchronizacji.
Programowanie funkcjonalne
Ponieważ programowanie deklaratywne nie może zawierać pętli, jedynym sposobem na iterację jest rekurencja funkcjonalna. W tym sensie programowanie funkcjonalne jest powiązane z programowaniem deklaratywnym.
Programowanie funkcjonalne zwykle sprawia, że funkcja jest obiektem pierwszej klasy, co oznacza, że typ funkcji może pojawić się w gramatyce w dowolnym miejscu. Rezultatem jest to, że funkcje mogą wprowadzać i działać na funkcjach, zapewniając w ten sposób rozdzielenie obaw przez podkreślenie składu funkcji, tj. Oddzielenie zależności między podliczeniami obliczeń deterministycznych.
Na przykład zamiast pisać osobną funkcję (i stosować rekurencję zamiast pętli, jeśli funkcja musi być również deklaratywna) dla każdej z nieskończonej liczby możliwych specjalistycznych akcji, które można zastosować do każdego elementu kolekcji, programowanie funkcjonalne wykorzystuje iterację wielokrotnego użytku funkcje, na przykład map, fold, filter. Te funkcje iteracji wprowadzają najwyższej klasy wyspecjalizowaną funkcję akcji. Te funkcje iteracji iterują kolekcję i wywołują specjalną funkcję wejściową dla każdego elementu. Te funkcje akcji są bardziej zwięzłe, ponieważ nie muszą już zawierać instrukcji pętli w celu iteracji kolekcji.
Zauważ jednak, że jeśli funkcja nie jest czysta, to tak naprawdę jest to procedura. Być może możemy argumentować, że programowanie funkcjonalne, które wykorzystuje nieczyste funkcje, to tak naprawdę programowanie proceduralne. Zatem jeśli zgodzimy się, że wyrażenia deklaratywne są RT, to możemy powiedzieć, że programowanie proceduralne nie jest programowaniem deklaratywnym, a zatem możemy argumentować, że programowanie funkcjonalne jest zawsze RT i musi być podzbiorem programowania deklaratywnego.
Równoległość
Ta funkcjonalna kompozycja z pierwszorzędnymi funkcjami może wyrazić głębokość w równoległości poprzez oddzielenie niezależnej funkcji.
Zasada Brenta: obliczenia z pracy w i głębokości d mogą być zaimplementowane w P-procesorze PRAM w czasie O (maks. (W / p, d)).
Skąd więc wzięło się to niebezpieczne założenie, że równoległość == Współbieżność? Jest to naturalna konsekwencja języków z efektami ubocznymi: kiedy twój język ma skutki uboczne wszędzie, wtedy za każdym razem, gdy próbujesz zrobić więcej niż jedną rzecz naraz, zasadniczo nie masz determinizmu spowodowanego przeplataniem efektów z każdej operacji . Tak więc w językach o skutkach ubocznych jedynym sposobem na uzyskanie równoległości jest współbieżność; nic więc dziwnego, że często spotykamy się z dwoma połączonymi.
Kolejność oceny PR
Należy zauważyć, że kolejność oceny wpływa również na skutki uboczne zakończenia i działania kompozycji funkcjonalnej.
Chętni (CBV) i leniwi (CBN) są kategorycznymi pojedynkami [ 10 ], ponieważ mają odwrócony porządek oceny, tj. Czy funkcje zewnętrzne lub wewnętrzne są odpowiednio oceniane jako pierwsze. Wyobraź sobie drzewo do góry nogami, a następnie chętny ocenia od gałęzi drzewa funkcji przechodzącej w górę hierarchii gałęzi do pnia funkcji najwyższego poziomu; podczas gdy leniwy ocenia od pnia do wierzchołków gałęzi. Chętny nie ma produktów łącznych („i”, a / k / a kategorycznych „produktów”), a leniwy nie ma rozłącznych produktów ubocznych („lub”, a / k / a jakościowych „sum”) [ 11 ].
Wydajność
Chętny
Podobnie jak w przypadku nieterminacji, chętny jest zbyt chętny z łączącą kompozycją funkcjonalną, tj. Struktura kontroli składu wykonuje niepotrzebną pracę, która nie jest wykonywana z leniwością. Na przykład , chętnie i niepotrzebnie odwzorowuje całą listę na booleany, gdy składa się ona z zagięcia kończącego się na pierwszym prawdziwym elemencie.
Ta niepotrzebna praca jest przyczyną domniemanego „do” dodatkowego logarytmicznego współczynnika złożoności czasowej złożoności czasowej chętnej i leniwej, obie z czystymi funkcjami. Rozwiązaniem jest użycie funktorów (np. List) z leniwymi konstruktorami (tj. Chętnych z opcjonalnymi leniwymi produktami), ponieważ z chętnością niepoprawność wynika z funkcji wewnętrznej. Wynika to z tego, że produkty są konstruktywnymi typami, tj. Typami indukcyjnymi z początkową algebrą w początkowym punkcie stałym [ 11 ]
Leniwy
Podobnie jak w przypadku nieterminacji, leniwy jest zbyt leniwy z dysocjatywną kompozycją funkcjonalną, tj. Koindukcyjna finalność może wystąpić później niż to konieczne, co skutkuje zarówno niepotrzebną pracą, jak i niedeterminizmem spóźnienia, co nie ma miejsca w przypadku chętnych [ 10 ] [ (patrz przykład w sekcji Nieterminacja, gdzie == jest zewnętrzną funkcją operatora binarnego). Wynika to z faktu, że koprodukty są ograniczone przez ostateczność, tj. Typy koindukcyjne z ostateczną algebrą na ostatecznym obiekcie [ 11 ]. 11 ] . Przykładami ostateczności są wyjątki dotyczące stanu, czasu, braku rozwiązania i środowiska wykonawczego. Są to bezwzględne skutki uboczne, ale nawet w czysto deklaratywnym języku (np. Haskell) istnieje imperatywna monada we / wy (uwaga: nie wszystkie monady są imperatywne!) Domyślnie w przydziale przestrzeni, a czas jest stanem w stosunku do imperatywu prawdziwy świat. Używanie leniwego nawet z opcjonalnymi chętnymi koproduktami przecieka „lenistwo” do wewnętrznych koproduktów, ponieważ z leniwością niepoprawność pochodzi z funkcji zewnętrznej
Leniwy powoduje nieokreśloność w projektowaniu i debugowaniu funkcji opóźnień i przestrzeni, których debugowanie prawdopodobnie wykracza poza możliwości większości programistów, z powodu dysonansu między zadeklarowaną hierarchią funkcji a kolejnością oceny środowiska wykonawczego. Leniwe czyste funkcje ocenione z entuzjazmem, potencjalnie mogłyby wprowadzić wcześniej niewidoczne zakończenie w czasie wykonywania. I odwrotnie, chętne czyste funkcje ocenione leniwie mogą potencjalnie wprowadzić wcześniej niewidoczną nieokreśloność przestrzeni i opóźnienia w czasie wykonywania.
Brak wypowiedzenia
W czasie kompilacji, z powodu problemu zatrzymania i wzajemnej rekurencji w pełnym języku Turinga, funkcje zasadniczo nie mogą zostać zakończone.
Chętny
Z chętny ale nie leniwy, dla koniunkcji Head„i” Tail, jeśli jedna Headlub Tailnie kończą, a następnie odpowiednio albo List( Head(), Tail() ).tail == Tail()czy List( Head(), Tail() ).head == Head()nie jest prawdą, ponieważ po lewej stronie nie ma, a prawa strona ma wypowiedzieć.
Natomiast z leniwymi obie strony kończą. Dlatego chętny jest zbyt chętny do stosowania produktów łącznych i nie przerywa (w tym wyjątków czasu wykonywania) w przypadkach, gdy nie jest to konieczne.
Leniwy
Z leniwym, ale nie chętnym, dla rozłączenia 1„lub” 2, jeśli fsię nie kończy, List( f ? 1 : 2, 3 ).tail == (f ? List( 1, 3 ) : List( 2, 3 )).tailto nie jest prawdą, ponieważ kończy się lewa strona, a prawa nie.
Natomiast gdy chętny żadna ze stron nie kończy, więc test równości nigdy nie jest osiągany. Tak więc leniwy jest zbyt leniwy z rozłącznymi koproduktami iw tych przypadkach nie kończy się (włączając wyjątki czasu wykonywania) po wykonaniu większej ilości pracy, niż chętniej by to zrobił.
[ 10 ] Deklaratywne kontynuacje i dualność kategoryczna, Filiński, sekcje 2.5.4 Porównanie CBV i CBN oraz 3.6.1 CBV i CBN w SCL.
[ 11 ] Deklaratywne kontynuacje i dualność kategoryczna, Filiński, sekcje 2.2.1 Produkty i koprodukty, 2.2.2 Obiekty końcowe i początkowe, 2.5.2 CBV z leniwymi produktami i 2.5.3 CBN z chętnymi koproduktami.
Nawet przy deklaratywnym programowaniu ograniczeń, ograniczenia nie mutują się, gdy solver znajduje rozwiązanie. Jest to oczywiste, ponieważ nie ma sposobu, aby określić czas ich zmiany. Nawet określone ograniczenia względem innych ograniczeń wszystkie są określone przed uruchomieniem solvera w celu znalezienia rozwiązania. Jest to analogiczne do deklaratywnych wzorów w arkuszu kalkulacyjnym .
Shelby Moore III,
3
Skrót nie oznacza podania definicji. Tam, gdzie napisałem, „RT jest często skracane„ brak efektów ubocznych ”, nie oznacza to, że definicja RT to„ brak efektów ubocznych ”, ponieważ ludzie mogą mieć różne definicje„ efektów ”. Jeśli zamiast tego powiedziałem: „RT jest często skrótem„ xyz ””, bezsensowny symbol nie podaje RT żadnej definicji. RT ma precyzyjną definicję, która nigdy się nie zmienia, bez względu na to, jakiego symbolu się używa.
Shelby Moore III,
Nie jestem w stanie znaleźć kontrprzykładu dla mojego twierdzenia, że każdy rodzaj DP jest RT. Na przykład znaczenie (tj. Wartość) gramatyki kontekstowej nie ulega mutacji w innym czasie lub pozycji w gramatyce. Zobacz mój komentarz dotyczący programowania ograniczeń powyżej.
Shelby Moore III,
1
Zrównanie C w stylu ESP z RT w monadzie stanu jest niepoprawne , ponieważ każda instrukcja C może mutować stan globalny, podczas gdy „wewnątrz” monady stanu każda odpowiednia instrukcja generuje KOPIĘ stanu (tak zmodyfikowanego). Ten drugi jest RT - pierwszy nie. Kompozycja monadyczna ma zawsze RT. DP == RT jest jedynym znaczeniem dla DP, które jest rozłącznym zestawem atrybutów (dowód matematyczny mam rację, w przeciwnym razie DP jest bez znaczenia).
Shelby Moore III,
1
Chciałbym zrozumieć tę przeszłość po pierwszym zdaniu. Czytałem instrukcję obsługi języka DAX, która wskazała, że jest to „język funkcjonalny”. Co to znaczy? Nie wiem, idź zapytaj swojego ojca.
Nick.McDermaid
103
Tak naprawdę nie ma w tym żadnej niejednoznacznej, obiektywnej definicji. Oto jak ja byłoby je określić:
Imperatyw - Koncentruje się na jakie kroki komputer powinien raczej niż to, co komputer będzie robić (ex C, C ++, Java.).
Deklaratywny - Koncentruje się na tym, co powinien zrobić komputer, a nie na tym, jak powinien to zrobić (np. SQL).
Funkcjonalny - podzbiór języków deklaratywnych, który kładzie duży nacisk na rekurencję
Pamiętaj o kilku rzeczach: 1) wyjaśnienie ma być proste, a nie kompleksowe 2), jak powiedziałem, istnieje wiele sposobów definiowania tych języków. Tak więc odpowiedź może być bardzo zła dla ciebie i dla kogoś innego.
Jason Baker
3
Programowanie funkcjonalne nie jest „podzbiorem języków deklaratywnych”. Programowanie deklaratywne wymaga niezmienności przechowywanych wartości, programowanie funkcjonalne nie, jeśli nie jest to czysty FP. Zobacz moją odpowiedź . Zobacz także wyjaśnienie dotyczące komórek arkusza kalkulacyjnego . Prawidłowe definicje celów nie są „dwuznaczne”. Programowanie imperatywne koncentruje się również „na tym, co powinien zrobić komputer”. Tylko rozróżnienie jest konieczne programowanie ma do czynienia z modyfikowalnych przechowywanych wartości.
Shelby Moore III,
5
@ShelbyMooreIII - W tej sprawie zgadzam się z Erikiem Meijerem. Nie ma tak naprawdę „nieczystego języka funkcjonalnego”. O ile mi wiadomo, Ocaml, F # i tym podobne są językami imperatywnymi z funkcjonalnymi strukturami danych. Ale jak powiedziałem w mojej odpowiedzi, nie wierzę, że istnieje jakakolwiek obiektywna, niejednoznaczna odpowiedź na to pytanie. Istnieje wiele sposobów definiowania rzeczy.
Jason Baker,
3
Można matematycznie udowodnić, że łączy on terminy, kiedy żadna z definicji nie jest jednoznaczna, ponieważ wybrane atrybuty nie są zbiorem rozłącznym. Jeśli zdefiniujesz FP jako czysty FP (tj. RT), to nie różni się on od DP, por. moja odpowiedź . Rozłączne atrybuty FP obejmują typ funkcji pierwszej klasy, który może być funkcją imperatywną. Tu i tutaj znalazłem bardziej fundamentalną niejednoznaczność . Preferowanie czystego FP jest prostopadłe do definicji samego FP.
Shelby Moore III,
21
@ShelbyMooreIII - zakładałem, że OP chce jego odpowiedzi w języku angielskim, a nie Math Nerd-ese. Jeśli to było nieprawidłowe założenie, przepraszam.
Jason Baker,
54
imperative i deklarative opisują dwa przeciwstawne style programowania. imperatyw jest tradycyjnym podejściem „krok po kroku”, podczas gdy deklaratywnym jest więcej „tego właśnie chcę, teraz wymyślicie, jak to zrobić”.
te dwa podejścia występują podczas programowania - nawet w tym samym języku i tym samym programie. ogólnie rzecz biorąc, podejście deklaratywne jest uważane za preferowane, ponieważ uwalnia programistę od konieczności podawania tak wielu szczegółów, a jednocześnie ma mniejszą szansę na błędy (jeśli opisujesz pożądany wynik, a niektóre dobrze przetestowane automatyczne procesy mogą działać wstecz od tego do zdefiniuj kroki, a następnie możesz mieć nadzieję, że rzeczy są bardziej niezawodne niż konieczność określania każdego kroku ręcznie).
z drugiej strony imperatywne podejście zapewnia większą kontrolę na niskim poziomie - jest to „podejście mikromanagera” do programowania. i to może pozwolić programiście wykorzystać wiedzę o problemie, aby dać bardziej efektywną odpowiedź. więc nie jest niczym niezwykłym, że niektóre części programu są pisane w bardziej deklaratywnym stylu, ale bardziej krytyczne dla prędkości części są bardziej niezbędne.
jak możesz sobie wyobrazić, język, w którym piszesz program, wpływa na to, jak możesz być deklaratywny - język, który ma wbudowane „inteligentne” narzędzia do ustalania, co zrobić, biorąc pod uwagę opis wyniku, pozwoli na znacznie bardziej deklaratywny podejście, w którym programiści muszą najpierw dodać ten rodzaj inteligencji za pomocą imperatywnego kodu, zanim będą w stanie zbudować bardziej deklaratywną warstwę na wierzchu. na przykład język prolog jest uważany za bardzo deklaratywny, ponieważ ma wbudowany proces wyszukiwania odpowiedzi.
do tej pory zauważysz, że nie wspomniałem o programowaniu funkcjonalnym . to dlatego, że jest to termin, którego znaczenie nie jest bezpośrednio związane z pozostałymi dwoma. w najprostszym, funkcjonalnym programowaniu oznacza, że korzystasz z funkcji. w szczególności, że używasz języka, który obsługuje funkcje jako „wartości pierwszej klasy” - oznacza to, że nie tylko możesz pisać funkcje, ale możesz pisać funkcje, które zapisują funkcje (które zapisują funkcje, które ...) i przekazują funkcje do Funkcje. w skrócie - funkcje są tak elastyczne i powszechne, jak ciągi znaków i liczby.
może zatem wydawać się dziwne, że funkcjonalne, imperatywne i deklaratywne są często wymieniane razem. powodem tego jest konsekwentne doprowadzenie idei programowania funkcjonalnego „do skrajności”. funkcja, w najczystszym tego słowa znaczeniu, jest czymś z matematyki - rodzajem „czarnej skrzynki”, która pobiera pewne dane wejściowe i zawsze daje takie same wyniki. i tego rodzaju zachowanie nie wymaga przechowywania zmiennych. więc jeśli projektujesz język programowania, którego celem jest wdrożenie bardzo czystego, matematycznie wpłyniętego pojęcia programowania funkcjonalnego, ostatecznie odrzucasz, w dużej mierze, pojęcie wartości, które mogą się zmieniać (w pewnym, ograniczonym, technicznym sensie).
a jeśli to zrobisz - jeśli ograniczysz sposób, w jaki zmienne mogą się zmieniać - prawie przypadkowo skończysz zmuszając programistę do pisania programów, które są bardziej deklaratywne, ponieważ duża część programowania imperatywnego opisuje, jak zmieniają się zmienne, i nie możesz już Zrób to! więc okazuje się, że programowanie funkcjonalne - szczególnie programowanie w języku funkcjonalnym - ma tendencję do dawania bardziej deklaratywnego kodu.
podsumowując, a następnie:
imperatywny i deklaratywny to dwa przeciwstawne style programowania (te same nazwy są używane w językach programowania, które zachęcają do tych stylów)
programowanie funkcjonalne to styl programowania, w którym funkcje stają się bardzo ważne, w wyniku czego zmieniające się wartości stają się mniej ważne. ograniczona możliwość określania zmian wartości wymusza bardziej deklaratywny styl.
dlatego „programowanie funkcjonalne” jest często określane jako „deklaratywne”.
Najlepsze wyjaśnienie do tej pory. Wygląda na to, że Functional i OOP są ortogonalne względem trybu rozkazującego i rozkazującego.
Didier A.
Czy powiedziałbyś, że programowanie logiczne jest deklaratywne? A może sam jest ortogonalny?
Didier A.
51
W skrócie:
Imperatyw język specfies szereg instrukcji, że Wykonuje komputerowe w sekwencji (to zrobić, to zrobić).
Deklaratywny język deklaruje zbiór reguł o tym, co powinno doprowadzić do wyjścia, z którego wejścia (np. Jeśli masz, to wynik jest B). Silnik zastosuje te reguły do danych wejściowych i da wynik.
Język funkcjonalny deklaruje zbiór matematycznych / funkcji logicznych, które określają, jak wejście jest tłumaczona na wyjściu. na przykład. f (y) = y * y. jest to rodzaj języka deklaratywnego.
Programowanie funkcjonalne nie jest „rodzajem języka deklaratywnego”. Programowanie deklaratywne wymaga niezmienności przechowywanych wartości, a nieczyste programowanie funkcjonalne nie. Zobacz moją odpowiedź . Zobacz także wyjaśnienie dotyczące komórek arkusza kalkulacyjnego . Jedyny powód imperatyw logiczny (aka instrukcji) wykonać w kolejności jest to, że ze względu na obecność modyfikowalnych przechowywanych wartości, wynik jest zależny od kolejności oceny. Używając twojego słownictwa, „instrukcja” może (i „reguła” nie może) działać na zmiennych wartościach.
Shelby Moore III,
23
Imperatyw: jak osiągnąć nasz cel
Take the next customer from a list.
If the customer lives in Spain, show their details.
If there are more customers in the list, go to the beginning
Deklaratywny: co chcemy osiągnąć
Show customer details of every customer living in Spain
Opisujesz programowanie funkcjonalne kontra programowanie inne niż FP, a nie programowanie deklaratywne a programowanie imperatywne. Programowanie funkcjonalne jest prostopadłe do polaryzacji między programowaniem imperatywnym i deklaratywnym. Programowanie deklaratywne wymaga niezmienności przechowywanych wartości, a nieczyste programowanie funkcjonalne nie. Zobacz moją odpowiedź .
Shelby Moore III,
22
Programowanie imperatywne oznacza każdy styl programowania, w którym program składa się z instrukcji opisujących, jak będą wykonywane operacje wykonywane przez komputer .
Programowanie deklaratywne oznacza dowolny styl programowania, w którym twój program jest opisem problemu lub rozwiązania - ale nie określa wprost, w jaki sposób praca zostanie wykonana .
Programowanie funkcjonalne to programowanie poprzez ocenę funkcji i funkcji funkcji ... Ponieważ (ściśle zdefiniowane) programowanie funkcjonalne oznacza programowanie poprzez zdefiniowanie funkcji matematycznych wolnych od skutków ubocznych, więc jest to forma programowania deklaratywnego, ale nie jest to jedyny rodzaj programowania deklaratywnego .
Programowanie logiczne (na przykład w Prologu) jest inną formą programowania deklaratywnego. Polega na obliczeniach, decydując, czy stwierdzenie logiczne jest prawdziwe (lub czy można je spełnić). Program jest zazwyczaj serią faktów i zasad - tzn. Opisem, a nie serią instrukcji.
Przepisywanie terminów (na przykład CASL) to kolejna forma deklaratywnego programowania. Obejmuje symboliczną transformację terminów algebraicznych. To całkowicie różni się od programowania logicznego i programowania funkcjonalnego.
Programowanie funkcjonalne nie jest „formą programowania deklaratywnego”. Programowanie deklaratywne wymaga niezmienności przechowywanych wartości, a nieczyste programowanie funkcjonalne nie. Zobacz moją odpowiedź . Zobacz także wyjaśnienie dotyczące komórek arkusza kalkulacyjnego . Termin „praca” w „opisać sposób wykonywania pracy” nie jest zdefiniowany. Jedyny powód imperatyw logiczny (aka „instrukcje”) wykonać w kolejności jest to, że ze względu na obecność modyfikowalnych przechowywanych wartości, wynik jest zależny od kolejności oceny.
Shelby Moore III,
2
Proszę przeczytać, że mówiłem o czystym programowaniu funkcjonalnym . Te paradygmaty mogą się przenikać i nie chcę zagłębiać się w porównywanie języków hybrydowych. Teoretycznie przynajmniej programowanie funkcjonalne dotyczy funkcji, a nie opisywania, w jaki sposób komputer wykona każde obliczenie - więc uważam, że jest deklaratywne.
Dafydd Rees,
Zredagowałem swoją odpowiedź i w sekcji „Programowanie funkcjonalne” dodałem scenariusz, w którym moglibyśmy argumentować, że FP jest zawsze czysty, a nieczysty FP to tak naprawdę „programowanie proceduralne”. Przepraszamy za wcześniejsze nieuwzględnienie tej interpretacji.
Shelby Moore III,
13
imperatyw - wyrażenia opisują sekwencję działań do wykonania (asocjacyjne)
deklaratywny - wyrażenia są deklaracjami, które przyczyniają się do zachowania programu (asocjacyjne, przemienne, idempotentne, monotoniczne)
funkcjonalny - wyrażenia mają wartość jako jedyny efekt; semantyka wspiera rozumowanie równań
Wyrażenia deklaratywne przyczyniają się do zamierzonego zachowania programu, tryb rozkazujący może przyczyniać się do zamierzonego lub niezamierzonego. Deklaratywna nie musi być przemienna i idempotentna, jeśli jest to celowa semantyka. Podoba mi się twoja zwięzła esencja funkcjonalna, więc ją oceniłem.
Shelby Moore III,
10
Ponieważ napisałem moją wcześniejszą odpowiedź, sformułowałem nową definicję właściwości deklaratywnej, która jest cytowana poniżej. Zdefiniowałem także programowanie imperatywne jako podwójną właściwość.
Ta definicja jest lepsza od tej, którą podałem w poprzedniej odpowiedzi, ponieważ jest zwięzła i bardziej ogólna. Ale może być trudniej zrozumieć, ponieważ implikacje twierdzeń o niekompletności mających zastosowanie do programowania i życia w ogólności są trudne dla ludzi, aby otoczyli umysł.
Cytowany wyjaśnienie definicji omawia rolę czysto funkcjonalne sztuki programowania w języku programowania deklaratywnego.
Wszystkie egzotyczne typy programowania pasują do następującej taksonomii deklaratywnej kontra imperatywnej, ponieważ następująca definicja twierdzi, że są dualistami.
Deklaratywny kontra imperatywny
Właściwość deklaratywna jest dziwna, tępa i trudna do uchwycenia w technicznie precyzyjnej definicji, która pozostaje ogólna i niejednoznaczna, ponieważ jest to naiwne przekonanie, że możemy zadeklarować znaczenie (aka semantyka) programu bez powodowania niezamierzonych skutków ubocznych. Istnieje nieodłączne napięcie między wyrażaniem znaczenia a unikaniem niezamierzonych efektów, a napięcie to faktycznie wywodzi się z twierdzeń o niekompletności programowania i naszego wszechświata.
Uproszczenie, nieprecyzyjne technicznie i często dwuznaczne jest definiowanie deklaratywnego jako „ co robić ”, a imperatywnego jako „ jak to zrobić ” . Niejednoznacznym przypadkiem jest „ co ” to „ jak ” w programie, który generuje program - kompilator.
Najwyraźniej nieograniczona rekurencja, która sprawia, że język Turinga jest kompletny , jest również analogicznie w semantyce - nie tylko w składniowej strukturze oceny (czyli semantyce operacyjnej). Jest to logicznie przykład analogiczny do twierdzenia Gödla - „ każdy kompletny system aksjomatów jest również niespójny ”. Zastanów się nad sprzecznością tego cytatu! Jest to także przykład, który pokazuje, w jaki sposób wyrażenie semantyki nie ma możliwego do udowodnienia ograniczenia, dlatego nie możemy udowodnić 2, że program (i analogicznie jego semantyka) zatrzymał aka twierdzenie Haltinga.
Twierdzenia o niekompletności wywodzą się z fundamentalnej natury naszego wszechświata, który, jak stwierdzono w Drugim Prawie Termodynamiki, brzmi „ entropia (czyli # niezależnych możliwości) na zawsze osiąga maksimum ”. Kodowanie i projektowanie programu nigdy się nie kończy - żyje! - ponieważ stara się zaspokoić potrzeby świata rzeczywistego, a semantyka świata rzeczywistego ciągle się zmienia i zyskuje na popularności. Ludzie nigdy nie przestają odkrywać nowych rzeczy (w tym błędów w programach ;-).
Aby precyzyjnie i technicznie uchwycić wyżej wspomniane pożądane pojęcie w tym dziwnym wszechświecie, który nie ma krawędzi (zastanów się, że! Nie ma „na zewnątrz” naszego wszechświata), wymagana jest zwięzła, ale zwodniczo-prosta definicja, która zabrzmi nieprawidłowo, dopóki nie zostanie wyjaśniona głęboko.
Definicja:
Właściwość deklaratywna to miejsce, w którym może istnieć tylko jeden możliwy zestaw instrukcji, które mogą wyrażać każdy konkretny semantyczny moduł.
Właściwością imperatywną 3 jest dualność, w której semantyka jest niespójna w składzie i / lub może być wyrażona za pomocą odmian zestawów instrukcji.
Ta definicja deklaratywna jest wyraźnie lokalna w zakresie semantycznym, co oznacza, że wymaga, aby semantyczny modułowy zachowywał swoje spójne znaczenie niezależnie od tego, gdzie i jak jest tworzony i stosowany w zakresie globalnym . Zatem każdy deklaratywny modułowy semantyczny powinien być z natury ortogonalny dla wszystkich możliwych innych - i nie być niemożliwym (z powodu twierdzeń o niekompletności) globalnym algorytmem lub modelem służącym do obserwowania spójności, co jest również punktem „ Profesora Roberta Harpera „ Więcej nie zawsze jest lepsze ” informatyki na Carnegie Mellon University, jednym z projektantów Standard ML.
Przykłady tej modułowej semantyki deklaratywnej obejmują funktory teorii kategorii, np .Applicative Typowanie nominalne, przestrzenie nazw, nazwane pola i wrt do poziomu operacyjnego semantyki, a następnie czystego programowania funkcjonalnego.
Tak więc dobrze zaprojektowane języki deklaratywne mogą wyraźniej wyrażać znaczenie , choć z pewną utratą ogólności w tym, co można wyrazić, ale zyskiem w tym, co można wyrazić z wewnętrzną spójnością.
Przykładem wyżej wymienionej definicji jest zestaw formuł w komórkach programu arkusza kalkulacyjnego - które nie powinny mieć takiego samego znaczenia po przeniesieniu do różnych komórek kolumny i wiersza, tj. Zmianie identyfikatorów komórek. Identyfikatory komórek są częścią zamierzonego znaczenia i nie są zbyteczne. Dlatego wynik każdego arkusza kalkulacyjnego jest unikalny dla identyfikatorów komórek w zestawie formuł. Spójnym semantycznym modułem w tym przypadku jest użycie identyfikatorów komórek jako danych wejściowych i wyjściowych funkcji czystych dla formuł komórek (patrz poniżej).
Hyper Text Markup Language, czyli HTML - język statycznych stron internetowych - jest przykładem bardzo (ale nie idealnie 3 ) języka deklaratywnego, który (przynajmniej przed HTML 5) nie był w stanie wyrazić zachowania dynamicznego. HTML jest chyba najłatwiejszym językiem do nauki. W celu zachowania dynamicznego imperatywny język skryptowy, taki jak JavaScript, był zwykle łączony z HTML. HTML bez JavaScript pasuje do deklaratywnej definicji, ponieważ każdy typ nominalny (tj. Tagi) zachowuje swoje spójne znaczenie w składzie zgodnie z regułami składni.
Konkurencyjną definicją deklaratywną są przemienne i idempotentne właściwości instrukcji semantycznych, tzn. Że instrukcje można zmieniać i duplikować bez zmiany znaczenia. Na przykład instrukcje przypisujące wartości do nazwanych pól mogą być ponownie uporządkowane i powielone bez zmiany znaczenia programu, jeśli te nazwy są modularne wrt w dowolnej dorozumianej kolejności. Nazwy czasem implikują kolejność, np. Identyfikatory komórek obejmują ich pozycję kolumny i wiersza - przesunięcie sumy w arkuszu kalkulacyjnym zmienia jego znaczenie. W przeciwnym razie właściwości te domyślnie wymagają globalnegospójność semantyki. Zasadniczo niemożliwe jest zaprojektowanie semantyki instrukcji, więc pozostają one spójne, jeśli są losowo uporządkowane lub duplikowane, ponieważ kolejność i duplikacja są nieodłączne od semantyki. Na przykład stwierdzenia „Foo istnieje” (lub konstrukcja) i „Foo nie istnieje” (i zniszczenie). Jeśli weźmie się pod uwagę przypadkową niekonsekwencję endemiczną za zamierzoną semantykę, wówczas uznaje się tę definicję za wystarczająco ogólną dla właściwości deklaratywnej. Zasadniczo definicja ta jest pusta jako definicja uogólniona, ponieważ próbuje ona uczynić spójność ortogonalną względem semantyki, tj. Przeciwstawić się temu, że wszechświat semantyki jest dynamicznie nieograniczony i nie może być ujęty w paradygmacie globalnej spójności.
Wymaganie komutatywnych i idempotentnych właściwości (porządek oceny strukturalnej) semantyki operacyjnej niższego poziomu przekształca semantykę operacyjną w deklaratywną zlokalizowaną modularną semantyczną, np. Czyste programowanie funkcjonalne (w tym rekurencyjne zamiast pętli imperatywnych). Następnie kolejność operacyjna szczegółów implementacji nie wpływa (tj. Rozprzestrzenia się globalnie ) na spójność semantyki wyższego poziomu. Na przykład kolejność oceny (i teoretycznie także powielania) formuł arkusza kalkulacyjnego nie ma znaczenia, ponieważ dane wyjściowe nie są kopiowane do danych wejściowych, dopóki nie zostaną obliczone wszystkie dane wyjściowe, tj. Analogicznie do funkcji czystych.
C, Java, C ++, C #, PHP i JavaScript nie są szczególnie deklaratywne. Składnia Copute'a i Pythona są bardziej deklaratywnie sprzężone z zamierzonymi wynikami , tj. Spójną semantyką składniową, która eliminuje obce, dzięki czemu można łatwo zrozumieć kod po zapomnieniu. Copute i Haskell egzekwują determinizm semantyki operacyjnej i zachęcają do „ nie powtarzania się ” (DRY), ponieważ pozwalają jedynie na paradygmat funkcjonalny.
2 Nawet tam, gdzie możemy udowodnić semantykę programu, np. Za pomocą języka Coq, jest to ograniczone do semantyki wyrażanej podczas pisania , a pisanie nigdy nie może uchwycić całej semantyki programu - nawet w przypadku języków, które są niekompletność Turinga, np. HTML + CSS, można wyrazić niespójne kombinacje, które mają w ten sposób niezdefiniowaną semantykę.
3 Wiele wyjaśnień niepoprawnie twierdzi, że tylko programowanie imperatywne ma składniowo uporządkowane instrukcje. Wyjaśniłem to zamieszanie między programowaniem imperatywnym a funkcjonalnym . Na przykład kolejność instrukcji HTML nie zmniejsza spójności ich znaczenia.
w programowaniu funkcjonalnym ... zakres zmienności zmiennej jest typem
W zależności od tego, jak odróżnia się programowanie funkcjonalne od programowania imperatywnego, twoje „przypisywalne” w programie imperatywnym może również mieć typ ograniczający jego zmienność.
Jedyną niezrozumiałą definicją, którą obecnie doceniam dla programowania funkcjonalnego, jest: a) funkcje jako obiekty i typy pierwszej klasy, b) preferencja dla rekurencji nad pętlami i / lub c) funkcje czyste - tj. Te funkcje, które nie wpływają na pożądaną semantykę programu po zapamiętywaniu (a zatem całkowicie czyste programowanie funkcjonalne nie istnieje w semantyce denotacyjnej ogólnego przeznaczenia z powodu wpływu semantyki operacyjnej, np. alokacji pamięci ).
Właściwość idempotentna funkcji czystej oznacza, że wywołanie funkcji jej zmiennych można zastąpić jej wartością, co zwykle nie ma miejsca w przypadku argumentów procedury imperatywnej. Funkcje czyste wydają się być deklaratywne w stosunku do nieskomplikowanych przejść stanu między typami danych wejściowych i wyników.
Ale zestaw funkcji czystych nie zachowuje takiej spójności, ponieważ możliwe jest modelowanie procesu imperatywnego skutków ubocznych (stanu globalnego) w czysto funkcjonalnym języku programowania, np. IOMonad Haskella, a ponadto całkowicie niemożliwe jest zapobieganie temu dowolny całkowicie funkcjonalny język programowania Turinga.
Jak napisałem w 2012 r., Który wydaje się podobny do konsensusu komentarzy na twoim niedawnym blogu , to programowanie deklaratywne jest próbą uchwycenia pojęcia, że zamierzona semantyka nigdy nie jest nieprzejrzysta. Przykładami nieprzejrzystej semantyki są zależność od porządku, zależność od usuwania semantyki wyższego poziomu w warstwie semantyki operacyjnej (np. Rzutowania nie są konwersjami, a potwierdzone generyczne ograniczają semantykę wyższego poziomu ) oraz zależność od wartości zmiennych, których nie można sprawdzić (udowodniono poprawne) według języka programowania.
Doszedłem zatem do wniosku, że tylko niekompletne języki mogą być deklaratywne.
Tak więc jednym jednoznacznym i wyraźnym atrybutem języka deklaratywnego może być to, że można udowodnić, że jego wyniki są zgodne z jakimś wymiennym zbiorem reguł generatywnych. Na przykład, dla każdego konkretnego programu HTML (ignorując różnice w sposobie rozbieżności interpretatorów), który nie jest skryptowany (tj. Nie jest kompletny w Turingu), jego zmienność wyjściowa może być policzalna. Lub bardziej zwięźle program HTML jest czystą funkcją jego zmienności. Podobnie program do obsługi arkuszy kalkulacyjnych jest czystą funkcją jego zmiennych wejściowych.
Wydaje mi się więc, że języki deklaratywne są antytezą
nieograniczonej rekurencji , tj. Według drugiego twierdzenia Gödela o samoreferencyjnych twierdzeniach nie można udowodnić.
Lesie Lamport napisała bajkę o tym, jak Euclid mógł obejść twierdzenia Gödela dotyczące niekompletności zastosowane do dowodów matematycznych w kontekście języka programowania poprzez zgodność między typami i logiką (korespondencja Curry-Howarda itp.).
dobry, ale lepszy, jeśli podasz co najmniej jeden przykład dla obu!
Pardeep Jain,
4
W dzisiejszych czasach nowy cel: potrzebujemy starych klasyfikacji?
W Imperatyw / deklaratywna / funkcjonalne aspekty było dobre w przeszłości do klasyfikowania języków rodzajowe, ale w dzisiejszych czasach „wielkiego wszystkim język” (jak Java, Python, JavaScript, itp) mają jakąś opcję (zazwyczaj ram ) wyrażenia z „innej ostrości” niż jego główny (zwykle imperatyw) i wyrażać równoległe procesy, funkcje deklaratywne, lambda itp.
Dobrym wariantem tego pytania jest: „Jaki aspekt jest dobry do klasyfikowania frameworków?”
... Ważnym aspektem jest coś, co możemy nazwać „stylem programowania” …
Zestaw podstawowych funkcji jQuery - wybór elementów DOM, przechodzenie i manipulacja - włączony przez silnik wyboru (...), stworzył nowy „styl programowania”, łącząc algorytmy i struktury danych DOM
Zatem jQuery jest najlepszym (popularnym) przykładem skoncentrowania się na „nowym stylu programowania” , który jest nie tylko orientacją obiektową, to „ Łączenie algorytmów i struktur danych ”. jQuery jest dość reaktywny, ponieważ arkusze kalkulacyjne, ale nie „zorientowany na komórki”, jest „ zorientowany na węzeł DOM ” ... Porównywanie głównych stylów w tym kontekście:
Bez fuzji : we wszystkich „dużych językach”, w dowolnym wyrażeniu funkcjonalnym / deklaratywnym / imperatywnym zwykle stosuje się „brak fuzji” danych i algorytmu, z wyjątkiem niektórych orientacji obiektowych, czyli fuzji z punktu widzenia ścisłej struktury algebrycznej .
Trochę fuzji : wszystkie klasyczne strategie fuzji, w dzisiejszych czasach mają trochę frameworku używającego go jako paradygmatu ... przepływ danych , programowanie sterowane zdarzeniami (lub stare języki specyficzne dla domeny, takie jak awk i XSLT ) ... Podobnie jak programowanie z nowoczesnymi arkuszami kalkulacyjnymi, są one również przykłady reaktywnego stylu programowania .
Wielka fuzja : to „styl jQuery” ... jQuery to język specyficzny dla domeny, skupiający się na „ algorytmach łączenia i strukturach danych DOM ”. PS: inne „języki zapytań”, takie jak XQuery, SQL (z PL jako opcję wyrażania rozkazującego) są również przykładami łączenia algorytmów danych, ale są to wyspy , bez fuzji z innymi modułami systemowymi ... Wiosna , przy użyciu opcji find()-wariantów i klauzule specyfikacji , to kolejny dobry przykład łączenia.
Programowanie deklaratywne polega na wyrażaniu pewnej ponadczasowej logiki między wejściem a wyjściem, na przykład w pseudokodzie następujący przykład byłby deklaratywny:
def factorial(n):
if n < 2:
return 1
else:
return factorial(n-1)
output = factorial(argvec[0])
Po prostu definiujemy tutaj relację zwaną „silnią”, a relację między wyjściem a danymi wejściowymi definiujemy jako tę relację. Jak powinno być tutaj oczywiste, o każdym języku strukturalnym zezwala w pewnym stopniu na programowanie deklaratywne. Główną ideą programowania deklaratywnego są dane niezmienne, jeśli przypiszesz do zmiennej, zrobisz to tylko raz, a potem nigdy więcej. Inne, bardziej rygorystyczne definicje oznaczają, że mogą nie występować żadne skutki uboczne, języki te są czasami nazywane „czysto deklaratywnymi”.
Ten sam wynik w stylu nadrzędnym byłby:
a = 1
b = argvec[0]
while(b < 2):
a * b--
output = a
W tym przykładzie nie wyraziliśmy żadnej ponadczasowej statycznej logicznej zależności między wejściem a wyjściem, ręcznie zmieniliśmy adresy pamięci, aż jeden z nich uzyska pożądany wynik. Powinno być oczywiste, że wszystkie języki w pewnym stopniu dopuszczają semantykę deklaratywną, ale nie wszystkie zezwalają na konieczność, niektóre „czysto” deklaratywne języki dopuszczają skutki uboczne i mutacje.
Mówi się, że języki deklaratywne określają „co należy zrobić”, w przeciwieństwie do „jak to zrobić”, myślę, że to błędne, deklaratywne programy wciąż określają sposób, w jaki należy przejść od wejścia do wyjścia, ale w inny sposób określona przez Ciebie relacja musi być efektywnie obliczalna (ważny termin, wyszukaj go, jeśli go nie znasz). Innym podejściem jest niedeterministyczne programowanie, które tak naprawdę określa warunki, które wynik musi spełnić, zanim twoja implementacja wyczerpie wszystkie ścieżki prób i błędów aż do sukcesu.
Języki czysto deklaratywne obejmują Haskell i Pure Prolog. Przesuwna skala od jednego do drugiego to: Pure Prolog, Haskell, OCaml, Scheme / Lisp, Python, JavaScript, C--, Perl, PHP, C ++, Pascall, C, Fortran, Assembly
Nie zdefiniowałeś programowania funkcjonalnego. Niepoprawnie zasugerowałeś „niektóre” wyłącznie „deklaratywne języki”, że programowanie deklaratywne może być nieczyste . Programowanie deklaratywne wymaga niezmienności przechowywanych wartości, programowanie imperatywne nie. Zobacz moją odpowiedź . Niezmienność jest cechą „ponadczasową” - przekonaj się, że twoja deklaratywność factorialnie mutuje żadnej wartości.
Shelby Moore III,
3
Kilka dobrych odpowiedzi tutaj dotyczących zanotowanych „typów”.
Przesyłam dodatkowe, bardziej „egzotyczne” koncepcje często związane z tłumem programistów funkcjonalnych:
Język specyficzny dla domeny lub programowanie DSL : tworzenie nowego języka, aby poradzić sobie z danym problemem.
Metaprogramowanie : gdy Twój program pisze inne programy.
Programowanie ewolucyjne : gdzie budujesz system, który stale się ulepsza lub generuje kolejne, lepsze generacje podprogramów.
Myślę, że twoja taksonomia jest nieprawidłowa. Istnieją dwa przeciwne typy: imperatywny i deklaratywny. Funkcjonalny jest tylko podtypem deklaratywnym. BTW, wikipedia podaje ten sam fakt.
FP nie jest „tylko podtypem deklaratywnym”. FP jest prostopadła do polaryzacji imperatywu vs. DP. DP wymaga niezmienności przechowywanych wartości, nieczyste FP nie. Wikipedia łączy FP z czystym FP, z absurdalnym twierdzeniem, że następujące pojęcia są „zasadniczo obce programowaniu imperatywnemu”: funkcje pierwszej klasy, rekurencja, kolejność oceny i pisanie statyczne. Następnie Wikipedia przyznaje nieczyste „Programowanie funkcjonalne w językach niefunkcjonalnych”.
Shelby Moore III,
Wikipedia nie ma racji w tej kwestii. Wiele popularnych języków funkcjonalnych umożliwia programowanie w „stylu deklaratywnym”, jeśli wybierzesz, ale nie są to języki deklaratywne. Ale to samo można powiedzieć o C, gdzie nadal możesz programować w funkcjonalnym stylu, jeśli wybierzesz, używając void * s.
Plynx
Prawdopodobnie powinienem był być bardziej zrozumiały w tej kwestii, ale z drugiej strony nie będę bałaganu na początku tematu z niezbyt istotnymi (imo) szczegółami. Widzę, że języki funkcjonalne są zwykle używane deklaratywnie. Możesz spróbować napisać deklaratywnie i / lub funkcjonalnie w ASM lub C lub prawdopodobnie możesz napisać program rozkazujący w Lisp, ale wątpię, czy byłby on bardzo pomocny lub pouczający dla autora pytania. Zasadniczo uważam jednak, że moja odpowiedź jest odpowiednia, nawet jeśli mogłaby być sformułowana inaczej.
Rorick
2
W skrócie, im bardziej styl programowania podkreśla Co (robić) abstrahując szczegóły Jak (zrobić), tym bardziej ten styl jest uważany za deklaratywny. Przeciwnie, jest konieczne w przypadku rozkazów. Programowanie funkcjonalne jest powiązane ze stylem deklaratywnym.
Zobacz moje komentarze poniżej innych odpowiedzi. FP nie zawsze jest deklaratywna. Co vs. jak niewłaściwa taksonomia dla własności intelektualnej a własność intelektualna, ponieważ zarówno DP, jak i własność intelektualna mają logikę obejmującą to, co i jak.
Odpowiedzi:
W chwili pisania tego tekstu najlepiej głosowane odpowiedzi na tej stronie są nieprecyzyjne i niejasne w definicji deklaratywnej vs. imperatywnej, w tym odpowiedzi, która cytuje Wikipedię. Niektóre odpowiedzi łączą warunki na różne sposoby.
Zobacz także moje wyjaśnienie, dlaczego programowanie w arkuszach kalkulacyjnych jest deklaratywne, niezależnie od tego, że formuły mutują komórki.
Ponadto kilka odpowiedzi twierdzi, że programowanie funkcjonalne musi być podzbiorem deklaratywnym. Od tego momentu zależy, czy odróżnimy „funkcję” od „procedury”. Najpierw zajmijmy się trybem rozkazującym vs. deklaratywnym.
Definicja wyrażenia deklaratywnego
Tylko atrybut, który może ewentualnie odróżnić deklaratywny wyraz z bezwzględnej wyrażenia jest referential przezroczystość (RT) podokna wyrażeń. Wszystkie pozostałe atrybuty są albo wspólne dla obu typów wyrażeń, albo pochodzą z RT.
Język w 100% deklaratywny (tj. Taki, w którym każde możliwe wyrażenie to RT) nie pozwala (wśród innych wymagań RT) na mutację przechowywanych wartości, np. HTML i większość Haskell.
Definicja ekspresji RT
RT jest często określane jako „bez skutków ubocznych”. Pojęcie efektów nie ma precyzyjnej definicji, więc niektórzy ludzie nie zgadzają się, że „brak efektów ubocznych” jest taki sam jak RT. RT ma precyzyjną definicję .
Ponieważ każde podwyrażenie jest koncepcyjnie wywołaniem funkcji, RT wymaga, aby implementacja funkcji (tj. Wyrażenie (-a) wewnątrz wywoływanej funkcji) nie mogła uzyskać dostępu do stanu zmiennego zewnętrznego względem funkcji (dostęp do zmiennego stanu lokalnego jest dozwolony). Mówiąc wprost, funkcja (implementacja) powinna być czysta .
Definicja funkcji czystej
Często mówi się, że czysta funkcja „nie ma skutków ubocznych”. Pojęcie efektów nie ma precyzyjnej definicji, więc niektórzy ludzie się z tym nie zgadzają.
Funkcje Pure mają następujące atrybuty.
Pamiętaj, że RT dotyczy wyrażeń (które obejmują wywołania funkcji), a czystość dotyczy (implementacji) funkcji.
Niejasnym przykładem nieczystych funkcji tworzących wyrażenia RT jest współbieżność, ale dzieje się tak, ponieważ czystość jest zepsuta na warstwie abstrakcji przerwań. Naprawdę nie musisz tego wiedzieć. Aby tworzyć wyrażenia RT, wywołujesz funkcje czyste.
Pochodne atrybuty RT
Wszelkie inne atrybuty cytowane w programowaniu deklaratywnym, np. Cytat z 1999 r. Używany przez Wikipedię, albo pochodzą z RT, albo są wspólne z programowaniem imperatywnym. To dowodzi, że moja precyzyjna definicja jest poprawna.
Uwaga: niezmienność wartości zewnętrznych stanowi podzbiór wymagań dla RT.
Języki deklaratywne nie mają struktur kontroli pętli, np.
for
Iwhile
ponieważ z powodu niezmienności warunek pętli nigdy się nie zmieni.Języki deklaratywne nie wyrażają przepływu sterującego innego niż kolejność zagnieżdżonych funkcji (inaczej zależności logiczne), ponieważ z powodu niezmienności inne wybory kolejności oceny nie zmieniają wyniku (patrz poniżej).
Języki deklaratywne wyrażają logiczne „kroki” (tj. Zagnieżdżoną kolejność wywołań funkcji RT), ale to, czy każde wywołanie funkcji jest semantyczne wyższego poziomu (tj. „Co robić”), nie jest wymogiem programowania deklaratywnego. Różnica od imperatywu polega na tym, że ze względu na niezmienność (tj. Bardziej ogólnie RT), te „kroki” nie mogą zależeć od stanu zmiennego, a jedynie od relacyjnej kolejności wyrażonej logiki (tj. Kolejności zagnieżdżania wywołań funkcji, czyli podwyrażeń ).
Na przykład akapit HTML
<p>
nie może zostać wyświetlony, dopóki nie zostaną ocenione wyrażenia podrzędne (tj. Znaczniki) w akapicie. Nie ma stanu zmiennego, tylko zależność od kolejności wynikająca z logicznej relacji hierarchii znaczników (zagnieżdżanie podwyrażeń, które są analogicznie zagnieżdżonymi wywołaniami funkcji ).Zatem istnieje atrybut pochodną niezmienność (ogólnie RT), to wyrażenia deklaratywne wyrażają tylko na logiczne związki z części składowych (tj argumentów Podfunkcja ekspresyjnych), a nie zmienne stanu związków.
Zamówienie oceny
Wybór kolejności oceny podwyrażeń może dawać różne wyniki tylko wtedy, gdy którekolwiek z wywołań funkcji nie jest RT (tj. Funkcja nie jest czysta), np. W obrębie funkcji można uzyskać dostęp do pewnego stanu zmiennego zewnętrznego względem funkcji.
Na przykład, biorąc pod uwagę kilka zagnieżdżonych wyrażeń, np
f( g(a, b), h(c, d) )
, chętny i leniwy ocenę argumentów funkcji dadzą takie same wyniki, jeśli te funkcjef
,g
ih
są czyste.Natomiast jeśli funkcje
f
,g
ih
nie są czyste, to wybór kolejności oceny może dać inny wynik.Uwaga: wyrażenia zagnieżdżone są funkcjami zagnieżdżonymi koncepcyjnie, ponieważ operatory wyrażeń są po prostu wywołaniami funkcji udającymi jednolity przedrostek, jednolity postfiks lub zapis binarny.
Stycznie, jeśli wszystkie identyfikatory, na przykład
a
,b
,c
,d
są niezmienne wszędzie stan zewnętrznego programu nie są dostępne (tj I / O) i nie dochodzi do pęknięcia warstwy abstrakcji, wtedy funkcje są zawsze czyste.Nawiasem mówiąc, Haskell ma inną składnię
f (g a b) (h c d)
.Szczegóły zamówienia ewaluacyjnego
Funkcja jest przejściem stanu (nie zmienną zapamiętaną wartością) z wejścia na wyjście. W przypadku kompozycji RT wywołań funkcji czystych kolejność wykonywania tych przejść stanu jest niezależna. Przejście stanu każdego wywołania funkcji jest niezależne od innych, ze względu na brak skutków ubocznych i zasadę, że funkcję RT można zastąpić jej wartością buforowaną . Aby skorygować popularne nieporozumienie , czysta kompozycja monadyczna jest zawsze deklaratywna i RT , pomimo faktu, że
IO
monada Haskella jest prawdopodobnie nieczysta, a zatem bezwzględnie konieczne jest, abyWorld
stan był zewnętrzny względem programu (ale w sensie poniższego zastrzeżenia, skutki uboczne są izolowane).Szybka ocena oznacza, że argumenty funkcji są oceniane przed wywołaniem funkcji, a leniwa ocena oznacza, że argumenty nie są oceniane, dopóki (i jeśli) są dostępne w obrębie funkcji.
Definicja : parametry funkcji są deklarowane w miejscu definicji funkcji , a argumenty funkcji są podawane w miejscu wywołania funkcji . Poznaj różnicę między parametrem a argumentem .
Koncepcyjnie, wszystkie wyrażenia są (kompozycja) wywołania funkcji, np stałe są funkcje bez nakładów, operatory jednoargumentowe są funkcje z jednego wejścia, dwuargumentowe operatory są funkcje z dwoma wejściami, konstruktorzy są funkcjami, oraz stwierdzenia nawet kontrolne (np
if
,for
,while
) można modelować za pomocą funkcji. Rozkaz, że te argumentów funkcji (nie mylić z zagnieżdżonych funkcji celu wywołania) są oceniane nie deklaruje składni, npf( g() )
mógłby chętnie oceniająg
następnief
nag
„s wyniku czy może to ocenićf
i ocenić tylko leniwieg
, gdy jego wynik jest potrzebny wewnątrzf
.Zastrzeżenie, brak pełnego języka Turinga (tzn. Który umożliwia nieograniczoną rekurencję) jest całkowicie deklaratywny, np. Leniwa ocena wprowadza nieokreśloność pamięci i czasu. Ale te skutki uboczne wynikające z wyboru kolejności oceny są ograniczone do zużycia pamięci, czasu wykonania, opóźnienia, braku terminacji i zewnętrznej histerezy , a zatem zewnętrznej synchronizacji.
Programowanie funkcjonalne
Ponieważ programowanie deklaratywne nie może zawierać pętli, jedynym sposobem na iterację jest rekurencja funkcjonalna. W tym sensie programowanie funkcjonalne jest powiązane z programowaniem deklaratywnym.
Ale programowanie funkcjonalne nie ogranicza się do programowania deklaratywnego . Kompozycję funkcjonalną można skontrastować z podtypami , szczególnie w odniesieniu do problemu ekspresji , w którym rozszerzenie można osiągnąć poprzez dodanie podtypów lub rozkład funkcjonalny . Rozszerzenie może stanowić połączenie obu metod.
Programowanie funkcjonalne zwykle sprawia, że funkcja jest obiektem pierwszej klasy, co oznacza, że typ funkcji może pojawić się w gramatyce w dowolnym miejscu. Rezultatem jest to, że funkcje mogą wprowadzać i działać na funkcjach, zapewniając w ten sposób rozdzielenie obaw przez podkreślenie składu funkcji, tj. Oddzielenie zależności między podliczeniami obliczeń deterministycznych.
Na przykład zamiast pisać osobną funkcję (i stosować rekurencję zamiast pętli, jeśli funkcja musi być również deklaratywna) dla każdej z nieskończonej liczby możliwych specjalistycznych akcji, które można zastosować do każdego elementu kolekcji, programowanie funkcjonalne wykorzystuje iterację wielokrotnego użytku funkcje, na przykład
map
,fold
,filter
. Te funkcje iteracji wprowadzają najwyższej klasy wyspecjalizowaną funkcję akcji. Te funkcje iteracji iterują kolekcję i wywołują specjalną funkcję wejściową dla każdego elementu. Te funkcje akcji są bardziej zwięzłe, ponieważ nie muszą już zawierać instrukcji pętli w celu iteracji kolekcji.Zauważ jednak, że jeśli funkcja nie jest czysta, to tak naprawdę jest to procedura. Być może możemy argumentować, że programowanie funkcjonalne, które wykorzystuje nieczyste funkcje, to tak naprawdę programowanie proceduralne. Zatem jeśli zgodzimy się, że wyrażenia deklaratywne są RT, to możemy powiedzieć, że programowanie proceduralne nie jest programowaniem deklaratywnym, a zatem możemy argumentować, że programowanie funkcjonalne jest zawsze RT i musi być podzbiorem programowania deklaratywnego.
Równoległość
Ta funkcjonalna kompozycja z pierwszorzędnymi funkcjami może wyrazić głębokość w równoległości poprzez oddzielenie niezależnej funkcji.
Zarówno współbieżność, jak i równoległość wymagają również programowania deklaratywnego , tj. Niezmienności i RT.
Kolejność oceny PR
Należy zauważyć, że kolejność oceny wpływa również na skutki uboczne zakończenia i działania kompozycji funkcjonalnej.
Chętni (CBV) i leniwi (CBN) są kategorycznymi pojedynkami [ 10 ], ponieważ mają odwrócony porządek oceny, tj. Czy funkcje zewnętrzne lub wewnętrzne są odpowiednio oceniane jako pierwsze. Wyobraź sobie drzewo do góry nogami, a następnie chętny ocenia od gałęzi drzewa funkcji przechodzącej w górę hierarchii gałęzi do pnia funkcji najwyższego poziomu; podczas gdy leniwy ocenia od pnia do wierzchołków gałęzi. Chętny nie ma produktów łącznych („i”, a / k / a kategorycznych „produktów”), a leniwy nie ma rozłącznych produktów ubocznych („lub”, a / k / a jakościowych „sum”) [ 11 ].
Wydajność
Chętny
Podobnie jak w przypadku nieterminacji, chętny jest zbyt chętny z łączącą kompozycją funkcjonalną, tj. Struktura kontroli składu wykonuje niepotrzebną pracę, która nie jest wykonywana z leniwością. Na przykład , chętnie i niepotrzebnie odwzorowuje całą listę na booleany, gdy składa się ona z zagięcia kończącego się na pierwszym prawdziwym elemencie.
Ta niepotrzebna praca jest przyczyną domniemanego „do” dodatkowego logarytmicznego współczynnika złożoności czasowej złożoności czasowej chętnej i leniwej, obie z czystymi funkcjami. Rozwiązaniem jest użycie funktorów (np. List) z leniwymi konstruktorami (tj. Chętnych z opcjonalnymi leniwymi produktami), ponieważ z chętnością niepoprawność wynika z funkcji wewnętrznej. Wynika to z tego, że produkty są konstruktywnymi typami, tj. Typami indukcyjnymi z początkową algebrą w początkowym punkcie stałym [ 11 ]
Leniwy
Podobnie jak w przypadku nieterminacji, leniwy jest zbyt leniwy z dysocjatywną kompozycją funkcjonalną, tj. Koindukcyjna finalność może wystąpić później niż to konieczne, co skutkuje zarówno niepotrzebną pracą, jak i niedeterminizmem spóźnienia, co nie ma miejsca w przypadku chętnych [ 10 ] [ (patrz przykład w sekcji Nieterminacja, gdzie == jest zewnętrzną funkcją operatora binarnego). Wynika to z faktu, że koprodukty są ograniczone przez ostateczność, tj. Typy koindukcyjne z ostateczną algebrą na ostatecznym obiekcie [ 11 ]. 11 ] . Przykładami ostateczności są wyjątki dotyczące stanu, czasu, braku rozwiązania i środowiska wykonawczego. Są to bezwzględne skutki uboczne, ale nawet w czysto deklaratywnym języku (np. Haskell) istnieje imperatywna monada we / wy (uwaga: nie wszystkie monady są imperatywne!) Domyślnie w przydziale przestrzeni, a czas jest stanem w stosunku do imperatywu prawdziwy świat. Używanie leniwego nawet z opcjonalnymi chętnymi koproduktami przecieka „lenistwo” do wewnętrznych koproduktów, ponieważ z leniwością niepoprawność pochodzi z funkcji zewnętrznej
Leniwy powoduje nieokreśloność w projektowaniu i debugowaniu funkcji opóźnień i przestrzeni, których debugowanie prawdopodobnie wykracza poza możliwości większości programistów, z powodu dysonansu między zadeklarowaną hierarchią funkcji a kolejnością oceny środowiska wykonawczego. Leniwe czyste funkcje ocenione z entuzjazmem, potencjalnie mogłyby wprowadzić wcześniej niewidoczne zakończenie w czasie wykonywania. I odwrotnie, chętne czyste funkcje ocenione leniwie mogą potencjalnie wprowadzić wcześniej niewidoczną nieokreśloność przestrzeni i opóźnienia w czasie wykonywania.
Brak wypowiedzenia
W czasie kompilacji, z powodu problemu zatrzymania i wzajemnej rekurencji w pełnym języku Turinga, funkcje zasadniczo nie mogą zostać zakończone.
Chętny
Z chętny ale nie leniwy, dla koniunkcji
Head
„i”Tail
, jeśli jednaHead
lubTail
nie kończą, a następnie odpowiednio alboList( Head(), Tail() ).tail == Tail()
czyList( Head(), Tail() ).head == Head()
nie jest prawdą, ponieważ po lewej stronie nie ma, a prawa strona ma wypowiedzieć.Natomiast z leniwymi obie strony kończą. Dlatego chętny jest zbyt chętny do stosowania produktów łącznych i nie przerywa (w tym wyjątków czasu wykonywania) w przypadkach, gdy nie jest to konieczne.
Leniwy
Z leniwym, ale nie chętnym, dla rozłączenia
1
„lub”2
, jeślif
się nie kończy,List( f ? 1 : 2, 3 ).tail == (f ? List( 1, 3 ) : List( 2, 3 )).tail
to nie jest prawdą, ponieważ kończy się lewa strona, a prawa nie.Natomiast gdy chętny żadna ze stron nie kończy, więc test równości nigdy nie jest osiągany. Tak więc leniwy jest zbyt leniwy z rozłącznymi koproduktami iw tych przypadkach nie kończy się (włączając wyjątki czasu wykonywania) po wykonaniu większej ilości pracy, niż chętniej by to zrobił.
[ 10 ] Deklaratywne kontynuacje i dualność kategoryczna, Filiński, sekcje 2.5.4 Porównanie CBV i CBN oraz 3.6.1 CBV i CBN w SCL.
[ 11 ] Deklaratywne kontynuacje i dualność kategoryczna, Filiński, sekcje 2.2.1 Produkty i koprodukty, 2.2.2 Obiekty końcowe i początkowe, 2.5.2 CBV z leniwymi produktami i 2.5.3 CBN z chętnymi koproduktami.
źródło
Tak naprawdę nie ma w tym żadnej niejednoznacznej, obiektywnej definicji. Oto jak ja byłoby je określić:
Imperatyw - Koncentruje się na jakie kroki komputer powinien raczej niż to, co komputer będzie robić (ex C, C ++, Java.).
Deklaratywny - Koncentruje się na tym, co powinien zrobić komputer, a nie na tym, jak powinien to zrobić (np. SQL).
Funkcjonalny - podzbiór języków deklaratywnych, który kładzie duży nacisk na rekurencję
źródło
imperative i deklarative opisują dwa przeciwstawne style programowania. imperatyw jest tradycyjnym podejściem „krok po kroku”, podczas gdy deklaratywnym jest więcej „tego właśnie chcę, teraz wymyślicie, jak to zrobić”.
te dwa podejścia występują podczas programowania - nawet w tym samym języku i tym samym programie. ogólnie rzecz biorąc, podejście deklaratywne jest uważane za preferowane, ponieważ uwalnia programistę od konieczności podawania tak wielu szczegółów, a jednocześnie ma mniejszą szansę na błędy (jeśli opisujesz pożądany wynik, a niektóre dobrze przetestowane automatyczne procesy mogą działać wstecz od tego do zdefiniuj kroki, a następnie możesz mieć nadzieję, że rzeczy są bardziej niezawodne niż konieczność określania każdego kroku ręcznie).
z drugiej strony imperatywne podejście zapewnia większą kontrolę na niskim poziomie - jest to „podejście mikromanagera” do programowania. i to może pozwolić programiście wykorzystać wiedzę o problemie, aby dać bardziej efektywną odpowiedź. więc nie jest niczym niezwykłym, że niektóre części programu są pisane w bardziej deklaratywnym stylu, ale bardziej krytyczne dla prędkości części są bardziej niezbędne.
jak możesz sobie wyobrazić, język, w którym piszesz program, wpływa na to, jak możesz być deklaratywny - język, który ma wbudowane „inteligentne” narzędzia do ustalania, co zrobić, biorąc pod uwagę opis wyniku, pozwoli na znacznie bardziej deklaratywny podejście, w którym programiści muszą najpierw dodać ten rodzaj inteligencji za pomocą imperatywnego kodu, zanim będą w stanie zbudować bardziej deklaratywną warstwę na wierzchu. na przykład język prolog jest uważany za bardzo deklaratywny, ponieważ ma wbudowany proces wyszukiwania odpowiedzi.
do tej pory zauważysz, że nie wspomniałem o programowaniu funkcjonalnym . to dlatego, że jest to termin, którego znaczenie nie jest bezpośrednio związane z pozostałymi dwoma. w najprostszym, funkcjonalnym programowaniu oznacza, że korzystasz z funkcji. w szczególności, że używasz języka, który obsługuje funkcje jako „wartości pierwszej klasy” - oznacza to, że nie tylko możesz pisać funkcje, ale możesz pisać funkcje, które zapisują funkcje (które zapisują funkcje, które ...) i przekazują funkcje do Funkcje. w skrócie - funkcje są tak elastyczne i powszechne, jak ciągi znaków i liczby.
może zatem wydawać się dziwne, że funkcjonalne, imperatywne i deklaratywne są często wymieniane razem. powodem tego jest konsekwentne doprowadzenie idei programowania funkcjonalnego „do skrajności”. funkcja, w najczystszym tego słowa znaczeniu, jest czymś z matematyki - rodzajem „czarnej skrzynki”, która pobiera pewne dane wejściowe i zawsze daje takie same wyniki. i tego rodzaju zachowanie nie wymaga przechowywania zmiennych. więc jeśli projektujesz język programowania, którego celem jest wdrożenie bardzo czystego, matematycznie wpłyniętego pojęcia programowania funkcjonalnego, ostatecznie odrzucasz, w dużej mierze, pojęcie wartości, które mogą się zmieniać (w pewnym, ograniczonym, technicznym sensie).
a jeśli to zrobisz - jeśli ograniczysz sposób, w jaki zmienne mogą się zmieniać - prawie przypadkowo skończysz zmuszając programistę do pisania programów, które są bardziej deklaratywne, ponieważ duża część programowania imperatywnego opisuje, jak zmieniają się zmienne, i nie możesz już Zrób to! więc okazuje się, że programowanie funkcjonalne - szczególnie programowanie w języku funkcjonalnym - ma tendencję do dawania bardziej deklaratywnego kodu.
podsumowując, a następnie:
imperatywny i deklaratywny to dwa przeciwstawne style programowania (te same nazwy są używane w językach programowania, które zachęcają do tych stylów)
programowanie funkcjonalne to styl programowania, w którym funkcje stają się bardzo ważne, w wyniku czego zmieniające się wartości stają się mniej ważne. ograniczona możliwość określania zmian wartości wymusza bardziej deklaratywny styl.
dlatego „programowanie funkcjonalne” jest często określane jako „deklaratywne”.
źródło
W skrócie:
Imperatyw język specfies szereg instrukcji, że Wykonuje komputerowe w sekwencji (to zrobić, to zrobić).
Deklaratywny język deklaruje zbiór reguł o tym, co powinno doprowadzić do wyjścia, z którego wejścia (np. Jeśli masz, to wynik jest B). Silnik zastosuje te reguły do danych wejściowych i da wynik.
Język funkcjonalny deklaruje zbiór matematycznych / funkcji logicznych, które określają, jak wejście jest tłumaczona na wyjściu. na przykład. f (y) = y * y. jest to rodzaj języka deklaratywnego.
źródło
Imperatyw: jak osiągnąć nasz cel
Deklaratywny: co chcemy osiągnąć
źródło
Programowanie imperatywne oznacza każdy styl programowania, w którym program składa się z instrukcji opisujących, jak będą wykonywane operacje wykonywane przez komputer .
Programowanie deklaratywne oznacza dowolny styl programowania, w którym twój program jest opisem problemu lub rozwiązania - ale nie określa wprost, w jaki sposób praca zostanie wykonana .
Programowanie funkcjonalne to programowanie poprzez ocenę funkcji i funkcji funkcji ... Ponieważ (ściśle zdefiniowane) programowanie funkcjonalne oznacza programowanie poprzez zdefiniowanie funkcji matematycznych wolnych od skutków ubocznych, więc jest to forma programowania deklaratywnego, ale nie jest to jedyny rodzaj programowania deklaratywnego .
Programowanie logiczne (na przykład w Prologu) jest inną formą programowania deklaratywnego. Polega na obliczeniach, decydując, czy stwierdzenie logiczne jest prawdziwe (lub czy można je spełnić). Program jest zazwyczaj serią faktów i zasad - tzn. Opisem, a nie serią instrukcji.
Przepisywanie terminów (na przykład CASL) to kolejna forma deklaratywnego programowania. Obejmuje symboliczną transformację terminów algebraicznych. To całkowicie różni się od programowania logicznego i programowania funkcjonalnego.
źródło
imperatyw - wyrażenia opisują sekwencję działań do wykonania (asocjacyjne)
deklaratywny - wyrażenia są deklaracjami, które przyczyniają się do zachowania programu (asocjacyjne, przemienne, idempotentne, monotoniczne)
funkcjonalny - wyrażenia mają wartość jako jedyny efekt; semantyka wspiera rozumowanie równań
źródło
Ponieważ napisałem moją wcześniejszą odpowiedź, sformułowałem nową definicję właściwości deklaratywnej, która jest cytowana poniżej. Zdefiniowałem także programowanie imperatywne jako podwójną właściwość.
Ta definicja jest lepsza od tej, którą podałem w poprzedniej odpowiedzi, ponieważ jest zwięzła i bardziej ogólna. Ale może być trudniej zrozumieć, ponieważ implikacje twierdzeń o niekompletności mających zastosowanie do programowania i życia w ogólności są trudne dla ludzi, aby otoczyli umysł.
Cytowany wyjaśnienie definicji omawia rolę czysto funkcjonalne sztuki programowania w języku programowania deklaratywnego.
Wszystkie egzotyczne typy programowania pasują do następującej taksonomii deklaratywnej kontra imperatywnej, ponieważ następująca definicja twierdzi, że są dualistami.
Edycja: Zamieściłem następujący komentarz na blogu Roberta Harpera:
źródło
Programowanie imperatywne: mówienie „maszynie”, jak coś zrobić, w wyniku czego stanie się to, co chcesz zrobić.
Programowanie deklaratywne: mówienie „maszynie”, co chcesz się wydarzyć, i pozwalanie komputerowi na określenie, jak to zrobić.
Przykład trybu rozkazującego
Przykład deklaratywny
Uwaga: Różnica nie polega na zwięzłości, złożoności czy abstrakcji. Jak już wspomniano, różnica polega na tym, jak vs co .
źródło
W dzisiejszych czasach nowy cel: potrzebujemy starych klasyfikacji?
W Imperatyw / deklaratywna / funkcjonalne aspekty było dobre w przeszłości do klasyfikowania języków rodzajowe, ale w dzisiejszych czasach „wielkiego wszystkim język” (jak Java, Python, JavaScript, itp) mają jakąś opcję (zazwyczaj ram ) wyrażenia z „innej ostrości” niż jego główny (zwykle imperatyw) i wyrażać równoległe procesy, funkcje deklaratywne, lambda itp.
Dobrym wariantem tego pytania jest: „Jaki aspekt jest dobry do klasyfikowania frameworków?” ... Ważnym aspektem jest coś, co możemy nazwać „stylem programowania” …
Skoncentruj się na połączeniu danych z algorytmem
Dobry przykład do wyjaśnienia. Jak możesz przeczytać o jQuery na Wikipedii ,
Zatem jQuery jest najlepszym (popularnym) przykładem skoncentrowania się na „nowym stylu programowania” , który jest nie tylko orientacją obiektową, to „ Łączenie algorytmów i struktur danych ”. jQuery jest dość reaktywny, ponieważ arkusze kalkulacyjne, ale nie „zorientowany na komórki”, jest „ zorientowany na węzeł DOM ” ... Porównywanie głównych stylów w tym kontekście:
Bez fuzji : we wszystkich „dużych językach”, w dowolnym wyrażeniu funkcjonalnym / deklaratywnym / imperatywnym zwykle stosuje się „brak fuzji” danych i algorytmu, z wyjątkiem niektórych orientacji obiektowych, czyli fuzji z punktu widzenia ścisłej struktury algebrycznej .
Trochę fuzji : wszystkie klasyczne strategie fuzji, w dzisiejszych czasach mają trochę frameworku używającego go jako paradygmatu ... przepływ danych , programowanie sterowane zdarzeniami (lub stare języki specyficzne dla domeny, takie jak awk i XSLT ) ... Podobnie jak programowanie z nowoczesnymi arkuszami kalkulacyjnymi, są one również przykłady reaktywnego stylu programowania .
Wielka fuzja : to „styl jQuery” ... jQuery to język specyficzny dla domeny, skupiający się na „ algorytmach łączenia i strukturach danych DOM ”.
PS: inne „języki zapytań”, takie jak XQuery, SQL (z PL jako opcję wyrażania rozkazującego) są również przykładami łączenia algorytmów danych, ale są to wyspy , bez fuzji z innymi modułami systemowymi ... Wiosna , przy użyciu opcji
find()
-wariantów i klauzule specyfikacji , to kolejny dobry przykład łączenia.źródło
Programowanie deklaratywne polega na wyrażaniu pewnej ponadczasowej logiki między wejściem a wyjściem, na przykład w pseudokodzie następujący przykład byłby deklaratywny:
Po prostu definiujemy tutaj relację zwaną „silnią”, a relację między wyjściem a danymi wejściowymi definiujemy jako tę relację. Jak powinno być tutaj oczywiste, o każdym języku strukturalnym zezwala w pewnym stopniu na programowanie deklaratywne. Główną ideą programowania deklaratywnego są dane niezmienne, jeśli przypiszesz do zmiennej, zrobisz to tylko raz, a potem nigdy więcej. Inne, bardziej rygorystyczne definicje oznaczają, że mogą nie występować żadne skutki uboczne, języki te są czasami nazywane „czysto deklaratywnymi”.
Ten sam wynik w stylu nadrzędnym byłby:
W tym przykładzie nie wyraziliśmy żadnej ponadczasowej statycznej logicznej zależności między wejściem a wyjściem, ręcznie zmieniliśmy adresy pamięci, aż jeden z nich uzyska pożądany wynik. Powinno być oczywiste, że wszystkie języki w pewnym stopniu dopuszczają semantykę deklaratywną, ale nie wszystkie zezwalają na konieczność, niektóre „czysto” deklaratywne języki dopuszczają skutki uboczne i mutacje.
Mówi się, że języki deklaratywne określają „co należy zrobić”, w przeciwieństwie do „jak to zrobić”, myślę, że to błędne, deklaratywne programy wciąż określają sposób, w jaki należy przejść od wejścia do wyjścia, ale w inny sposób określona przez Ciebie relacja musi być efektywnie obliczalna (ważny termin, wyszukaj go, jeśli go nie znasz). Innym podejściem jest niedeterministyczne programowanie, które tak naprawdę określa warunki, które wynik musi spełnić, zanim twoja implementacja wyczerpie wszystkie ścieżki prób i błędów aż do sukcesu.
Języki czysto deklaratywne obejmują Haskell i Pure Prolog. Przesuwna skala od jednego do drugiego to: Pure Prolog, Haskell, OCaml, Scheme / Lisp, Python, JavaScript, C--, Perl, PHP, C ++, Pascall, C, Fortran, Assembly
źródło
factorial
nie mutuje żadnej wartości.Kilka dobrych odpowiedzi tutaj dotyczących zanotowanych „typów”.
Przesyłam dodatkowe, bardziej „egzotyczne” koncepcje często związane z tłumem programistów funkcjonalnych:
źródło
Myślę, że twoja taksonomia jest nieprawidłowa. Istnieją dwa przeciwne typy: imperatywny i deklaratywny. Funkcjonalny jest tylko podtypem deklaratywnym. BTW, wikipedia podaje ten sam fakt.
źródło
W skrócie, im bardziej styl programowania podkreśla Co (robić) abstrahując szczegóły Jak (zrobić), tym bardziej ten styl jest uważany za deklaratywny. Przeciwnie, jest konieczne w przypadku rozkazów. Programowanie funkcjonalne jest powiązane ze stylem deklaratywnym.
źródło