Zum Zeitpunkt der Erstellung dieses Artikels sind die am häufigsten gewählten Antworten auf dieser Seite ungenau und verworren in Bezug auf die Definition von deklarativ und imperativ, einschließlich der Antwort, die Wikipedia zitiert. In einigen Antworten werden die Begriffe auf unterschiedliche Weise miteinander vermischt.
Siehe auch meine Erklärung warum die Programmierung von Tabellenkalkulationen deklarativ ist, unabhängig davon, dass die Formeln die Zellen verändern.
Außerdem wird in mehreren Antworten behauptet, dass die funktionale Programmierung eine Teilmenge der deklarativen Programmierung sein muss. In diesem Punkt hängt es davon ab, ob wir "Funktion" von "Prozedur" unterscheiden. Behandeln wir zunächst den Unterschied zwischen imperativ und deklarativ.
Definition von deklarativer Ausdruck
El sólo Attribut, das möglicherweise eine Unterscheidung zwischen deklarativ Ausdruck aus einer Imperativ Ausdruck ist die referenzielle Transparenz (RT) seiner Unterausdrücke. Alle anderen Attribute werden entweder von beiden Arten von Ausdrücken gemeinsam genutzt oder von der RT abgeleitet.
Eine 100% deklarative Sprache (d.h. eine, in der jeder mögliche Ausdruck RT ist) erlaubt (neben anderen RT-Anforderungen) nicht die Mutation gespeicherter Werte, z.B. HTML und der größte Teil von Haskell.
Definition von RT-Ausdruck
Die RT wird oft als "nebenwirkungsfrei" bezeichnet. Der Begriff Auswirkungen hat keine genaue Definition, so dass einige Leute nicht damit einverstanden sind, dass "keine Nebenwirkungen" dasselbe ist wie RT. RT hat eine genaue Definition :
Ein Ausdruck e
ist referenziell transparent, wenn für alle Programme p
jedes Vorkommen von e
en p
kann durch das Ergebnis der Auswertung von e
, ohne das beobachtbare Ergebnis von p
.
Da jeder Unterausdruck konzeptionell ein Funktionsaufruf ist, verlangt RT, dass die Implementierung einer Funktion (d. h. der Ausdruck/die Ausdrücke innerhalb der aufgerufenen Funktion) nicht auf den veränderlichen Zustand zugreifen darf, der extern zur Funktion (Zugriff auf die änderbar . Staat erlaubt ist). Vereinfacht ausgedrückt, sollte die Funktion (Implementierung) sein rein .
Definition von reine Funktion
Von einer reinen Funktion wird oft gesagt, dass sie "keine Nebeneffekte" hat. Der Begriff Auswirkungen hat keine genaue Definition, so dass manche Leute damit nicht einverstanden sind.
Reine Funktionen haben die folgenden Eigenschaften.
- die einzige beobachtbare Ausgabe ist der Rückgabewert.
- die einzige Abhängigkeit von der Ausgabe sind die Argumente.
- Argumente vollständig bestimmt werden, bevor eine Ausgabe erzeugt wird.
Denken Sie daran, dass RT für Ausdrücke (einschließlich Funktionsaufrufe) und Reinheit für (Implementierungen von) Funktionen gilt.
Ein obskures Beispiel für unreine Funktionen, die RT-Ausdrücke bilden, ist die Gleichzeitigkeit, aber das liegt daran, dass die Reinheit auf der Unterbrechungsabstraktionsschicht gebrochen wird. Das brauchen Sie nicht wirklich zu wissen. Um RT-Ausdrücke zu erstellen, rufen Sie reine Funktionen auf.
Abgeleitete Attribute der RT
Jedes andere Attribut, das für die deklarative Programmierung genannt wird, z. B. das Zitat von 1999 von Wikipedia verwendet wird, entweder von der RT abgeleitet oder mit der imperativen Programmierung geteilt wird. Damit ist bewiesen, dass meine genaue Definition richtig ist.
Anmerkung: Unveränderlichkeit von externe Werte ist eine Teilmenge der Anforderungen für RT.
-
Deklarative Sprachen haben keine Kontrollstrukturen mit Schleifen, z. B. for
y while
porque aufgrund der Unveränderlichkeit würde sich die Schleifenbedingung nie ändern.
-
Deklarative Sprachen drücken keinen anderen Kontrollfluss aus als die Reihenfolge verschachtelter Funktionen (auch bekannt als logische Abhängigkeiten), weil aufgrund der Unveränderlichkeit Wenn Sie eine andere Auswertungsreihenfolge wählen, ändert sich das Ergebnis nicht (siehe unten).
-
Deklarative Sprachen drücken logische "Schritte" aus (d.h. die Reihenfolge der verschachtelten RT-Funktionsaufrufe), aber ob jeder Funktionsaufruf eine höhere semantische Ebene darstellt (d.h. "was zu tun ist"), ist keine Anforderung der deklarativen Programmierung. Der Unterschied zum Imperativ besteht darin, dass aufgrund der Unveränderlichkeit (d.h. allgemeiner RT) können diese "Schritte" nicht von einem veränderlichen Zustand abhängen, sondern nur von der relationalen Reihenfolge der ausgedrückten Logik (d.h. der Reihenfolge der Verschachtelung der Funktionsaufrufe, auch bekannt als Unterausdrücke).
Zum Beispiel, der HTML-Absatz <p>
kann erst angezeigt werden, wenn die Unterausdrücke (d. h. Tags) im Absatz ausgewertet wurden. Es gibt keinen veränderlichen Zustand, sondern nur eine Ordnungsabhängigkeit aufgrund der logischen Beziehung der Tag-Hierarchie (Verschachtelung von Unterausdrücken, die analog verschachtelte Funktionsaufrufe ).
- So gibt es das abgeleitete Attribut der Unveränderlichkeit (allgemeiner: RT), dass deklarative Ausdrücke, die sólo le site logisch Beziehungen der Bestandteile (d. h. der Funktionsargumente der Unterausdrücke) und nicht veränderlicher Zustand Beziehungen.
Bewertungsauftrag
Die Wahl der Auswertungsreihenfolge von Teilausdrücken kann nur dann zu einem abweichenden Ergebnis führen, wenn einer der Funktionsaufrufe nicht RT ist (d. h. die Funktion ist nicht rein), z. B. wenn innerhalb der Funktion auf einen veränderlichen Zustand außerhalb der Funktion zugegriffen wird.
Ein Beispiel: Bei einigen verschachtelten Ausdrücken, z. B. f( g(a, b), h(c, d) )
Die eifrige und die faule Auswertung der Funktionsargumente führen zu denselben Ergebnissen, wenn die Funktionen f
, g
y h
sind rein.
Wenn die Funktionen f
, g
y h
nicht rein sind, dann kann die Wahl der Auswertungsreihenfolge zu einem anderen Ergebnis führen.
Beachten Sie, dass verschachtelte Ausdrücke konzeptionell verschachtelte Funktionen sind, da Ausdrucksoperatoren nur Funktionsaufrufe sind, die sich als unäres Präfix, unäres Postfix oder binäre Infix-Notation tarnen.
Tangential, wenn alle Identifikatoren, z.B. a
, b
, c
, d
sind unveränderlich Überall dort, wo auf programmfremde Zustände nicht zugegriffen werden kann (z. B. E/A) und keine Abstraktionsschicht unterbrochen wird, sind die Funktionen immer rein.
Übrigens, Haskell hat eine andere Syntax, f (g a b) (h c d)
.
Details zum Bewertungsauftrag
Eine Funktion ist ein Zustandsübergang (kein veränderlicher gespeicherter Wert) von der Eingabe zur Ausgabe. Für RT-Kompositionen von Aufrufen zu rein Funktionen ist die Reihenfolge der Ausführung dieser Zustandsübergänge unabhängig. Der Zustandsübergang eines jeden Funktionsaufrufs ist unabhängig von den anderen, da es keine Seiteneffekte gibt und der Grundsatz gilt, dass ein RT-Funktion kann durch ihren zwischengespeicherten Wert ersetzt werden . An ein verbreitetes Missverständnis zu korrigieren ist die reine monadische Komposition immer deklarativ und RT trotz der Tatsache, dass Haskell's IO
Monade ist wohl unrein und damit zwingend erforderlich in Bezug auf die World
Zustand außerhalb des Programms (aber im Sinne des nachstehenden Vorbehalts sind die Nebeneffekte isoliert).
Eifrige Auswertung bedeutet, dass die Funktionsargumente ausgewertet werden, bevor die Funktion aufgerufen wird, und faule Auswertung bedeutet, dass die Argumente werden nicht ausgewertet bis (und wenn) auf sie innerhalb der Funktion zugegriffen wird.
Definition Funktion Parameter werden in der Funktion Definition Standort und Funktion Argumente werden bei der Funktion aufrufen Standort. Kennen Sie den Unterschied zwischen Parameter y Argument .
Konzeptionell sind alle Ausdrücke (eine Zusammensetzung von) Funktionsaufrufen, z. B. sind Konstanten Funktionen ohne Eingaben, unäre Operatoren sind Funktionen mit einer Eingabe, binäre Infix-Operatoren sind Funktionen mit zwei Eingaben, Konstruktoren sind Funktionen und sogar Steueranweisungen (z. B. if
, for
, while
) können mit Funktionen modelliert werden. Die anordnen, dass diese Argument Funktionen (nicht zu verwechseln mit verschachtelten Funktionsaufrufen) ausgewertet werden, wird nicht durch die Syntax angegeben, z. B. f( g() )
könnte eifrig bewerten g
dann f
auf g
Ergebnis auswerten oder es könnte f
und bewerten nur faul g
wenn sein Ergebnis innerhalb von f
.
Vorbehalt, nein Turing vollständig Sprache (d.h. die unbeschränkte Rekursion zulässt) vollkommen deklarativ ist, führt z.B. die faule Auswertung zu Speicher- und Zeitindeterminismus. Diese durch die Wahl der Auswertungsreihenfolge bedingten Nebeneffekte beschränken sich jedoch auf den Speicherverbrauch, die Ausführungszeit, die Latenzzeit, den Nichtabbruch und externe Hysterese also externe Synchronisation.
Funktionale Programmierung
Da es bei der deklarativen Programmierung keine Schleifen geben kann, ist die einzige Möglichkeit der Iteration die funktionale Rekursion. In diesem Sinne ist die funktionale Programmierung mit der deklarativen Programmierung verwandt.
Pero die funktionale Programmierung ist nicht auf die deklarative Programmierung beschränkt . Die funktionale Zusammensetzung kann sein im Gegensatz zur Subtypisierung insbesondere in Bezug auf die Ausdruck Problem wobei die Erweiterung erreicht werden kann durch entweder durch Hinzufügen von Subtypen oder durch funktionale Zerlegung . Erweiterung kann eine Mischung sein der beiden Methoden.
In der funktionalen Programmierung ist die Funktion in der Regel ein Objekt erster Klasse, d. h. der Funktionstyp kann in der Grammatik überall dort auftauchen, wo auch jeder andere Typ auftauchen kann. Das Ergebnis ist, dass Funktionen Funktionen eingeben und auf Funktionen operieren können, wodurch die Trennung von Problemen durch die Betonung der Funktionskomposition ermöglicht wird, d. h. die Trennung der Abhängigkeiten zwischen den Teilberechnungen einer deterministischen Berechnung.
Zum Beispiel, anstatt eine eigene Funktion zu schreiben (und unter Verwendung von Rekursion anstelle von Schleifen, wenn die Funktion auch deklarativ sein muss) für jede der unendlich vielen möglichen spezialisierten Aktionen, die auf jedes Element einer Sammlung angewendet werden könnten, verwendet die funktionale Programmierung wiederverwendbare Iterationsfunktionen, z. B. map
, fold
, filter
. Diese Iterationsfunktionen geben eine spezialisierte Aktionsfunktion erster Klasse ein. Diese Iterationsfunktionen iterieren die Sammlung und rufen die eingegebene spezialisierte Aktionsfunktion für jedes Element auf. Diese Aktionsfunktionen sind prägnanter, da sie keine Schleifenanweisungen mehr enthalten müssen, um die Sammlung zu iterieren.
Beachten Sie jedoch, dass es sich bei einer Funktion, die nicht rein ist, um eine Prozedur handelt. Wir können vielleicht argumentieren, dass funktionale Programmierung, die unreine Funktionen verwendet, in Wirklichkeit prozedurale Programmierung ist. Wenn wir also zustimmen, dass deklarative Ausdrücke RT sind, dann können wir sagen, dass prozedurale Programmierung nicht deklarative Programmierung ist, und so könnten wir argumentieren, dass funktionale Programmierung immer RT ist und eine Teilmenge der deklarativen Programmierung sein muss.
Parallelität
Diese funktionale Komposition mit Funktionen erster Klasse kann die Tiefe in der Parallelität indem die unabhängige Funktion herausgetrennt wird.
Brentsches Prinzip: Berechnungen mit Arbeit w und Tiefe d können in einem PRAM mit p-Prozessoren in der Zeit O(max(w/p, d)) durchgeführt werden.
Sowohl Gleichzeitigkeit als auch Parallelität eine deklarative Programmierung erfordern d.h. Unveränderlichkeit und RT.
Woher kommt also die gefährliche Annahme, dass Parallelität = Gleichzeitigkeit bedeutet? herkommt? Das ist eine natürliche Folge von Sprachen mit Seiteneffekten: Wenn Ihre Sprache überall Nebeneffekte hat, dann müssen Sie jedes Mal, wenn Sie versuchen mehr als eine Sache zur gleichen Zeit zu tun, haben Sie im Grunde Nicht-Determinismus, der durch die Verschachtelung der Effekte der einzelnen Operation. In Sprachen mit Seiteneffekten ist also die einzige Möglichkeit, Parallelität zu erreichen Parallelität zu erreichen; es ist daher nicht überraschend, dass wir dass wir die beiden oft vermischt sehen.
FP-Bewertungsauftrag
Beachten Sie, dass die Auswertungsreihenfolge auch Auswirkungen auf die Beendigung und die Leistungsnebeneffekte der funktionalen Komposition hat.
Eifrige (CBV) und faule (CBN) sind kategorische Duelle[ 10 ], weil sie eine umgekehrte Auswertungsreihenfolge haben, d.h. ob die äußeren oder inneren Funktionen zuerst ausgewertet werden. Stellen Sie sich einen auf dem Kopf stehenden Baum vor, dann wird eager von den Zweigspitzen des Funktionsbaums die Zweighierarchie hinauf bis zum Funktionsstamm der obersten Ebene ausgewertet, während lazy vom Stamm abwärts bis zu den Zweigspitzen ausgewertet wird. Eager kennt keine konjunktiven Produkte ("und", auch bekannt als kategorische "Produkte") und lazy kennt keine disjunktiven Koprodukte ("oder", auch bekannt als kategorische "Summen")[ 11 ].
Leistung
Wie bei der Nichtterminierung ist eager bei der konjunktiven funktionalen Komposition zu eifrig, d. h. die kompositionale Kontrollstruktur leistet unnötige Arbeit, die mit lazy nicht erledigt wird. Für Beispiel wird die gesamte Liste eifrig und unnötigerweise auf Boolesche Werte abgebildet, wenn sie mit einer Faltung zusammengestellt wird, die am ersten wahren Element endet.
Diese unnötige Arbeit ist die Ursache für die angegebenen "bis zu" zusätzlichen Kosten log n-Faktor in der sequentiellen Zeitkomplexität von eager versus lazy, beide mit reinen Funktionen. Eine Lösung besteht in der Verwendung von Funktoren (z. B. Listen) mit lazy-Konstruktoren (d. h. eager mit optionalen lazy-Produkten), da bei eager die Unrichtigkeit des Eifers von der inneren Funktion ausgeht. Dies liegt daran, dass Produkte konstruktive Typen sind, d. h. induktive Typen mit einer Anfangsalgebra auf einem Anfangsfixpunkt[ 11 ]
Wie bei der Non-Terminierung ist lazy bei der disjunktiven funktionalen Komposition zu faul, d.h. die koinduktive Finalität kann später als nötig eintreten, was sowohl zu unnötiger Arbeit als auch zur Nicht-Determiniertheit der Verspätung führt, was bei eager nicht der Fall ist[ 10 ][ 11 ]. Beispiele für Endgültigkeit sind Zustand, Zeitplanung, Nichtbeendigung und Laufzeitausnahmen. Dies sind imperative Nebeneffekte, aber selbst in einer rein deklarativen Sprache (z.B. Haskell) gibt es einen Zustand in der imperativen IO-Monade (Achtung: nicht alle Monaden sind imperativ!), implizit in der Raumzuweisung, und das Timing ist ein Zustand relativ zur imperativen realen Welt. Die Verwendung von lazy, selbst mit optionalen eager-Koprodukten, führt dazu, dass die "Faulheit" in die inneren Koprodukte übergeht, da bei lazy die Unkorrektheit der Faulheit stammt aus der äußeren Funktion (siehe das Beispiel im Abschnitt Nicht-Terminierung, wo == eine äußere binäre Operatorfunktion ist). Dies liegt daran, dass Koprodukte durch Finalität begrenzt sind, d. h. koinduktive Typen mit einer finalen Algebra auf ein finales Objekt[ 11 ].
Lazy verursacht Indeterminismus beim Entwurf und der Fehlersuche von Funktionen für Latenz und Raum, deren Fehlersuche wahrscheinlich die Fähigkeiten der meisten Programmierer übersteigt, da die Dissonanz zwischen die deklarierte Funktionshierarchie und die Reihenfolge der Auswertung zur Laufzeit. Reine Lazy-Funktionen, die mit Eager ausgewertet werden, können möglicherweise zu einem bisher nicht beobachteten Nicht-Abbruch zur Laufzeit führen. Umgekehrt könnten reine eager-Funktionen, die mit lazy ausgewertet werden, zur Laufzeit einen bisher nicht gesehenen Raum- und Latenzindeterminismus einführen.
Nichtkündigung
Zur Kompilierzeit kann aufgrund des Halting-Problems und der gegenseitigen Rekursion in einer vollständigen Turing-Sprache im Allgemeinen nicht garantiert werden, dass die Funktionen enden.
Mit eifrig, aber nicht faul, für die Verbindung von Head
"und" Tail
wenn entweder Head
o Tail
nicht abbricht, dann wird entweder List( Head(), Tail() ).tail == Tail()
o List( Head(), Tail() ).head == Head()
ist nicht wahr, weil die linke Seite nicht endet und die rechte Seite schon.
Bei Faulheit hingegen enden beide Seiten. Daher ist eager bei konjunktiven Produkten zu eifrig und terminiert nicht (einschließlich Laufzeitausnahmen) in den Fällen, in denen es nicht notwendig ist.
Mit faul, aber nicht begierig, auf die Disjunktion von 1
"oder" 2
, si f
nicht beendet wird, dann List( f ? 1 : 2, 3 ).tail == (f ? List( 1, 3 ) : List( 2, 3 )).tail
ist nicht wahr, weil die linke Seite abbricht und die rechte nicht.
Bei eager hingegen bricht keine der beiden Seiten ab, so dass der Gleichheitstest nie erreicht wird. Daher ist lazy bei disjunktiven Koprodukten zu träge und bricht in diesen Fällen nicht ab (einschließlich Laufzeitausnahmen), nachdem es mehr Arbeit gemacht hat, als es eager getan hätte.
[ 10 ] Declarative Continuations and Categorical Duality, Filinski, Abschnitte 2.5.4 A comparison of CBV and CBN, und 3.6.1 CBV and CBN in the SCL.
[ 11 ] Declarative Continuations and Categorical Duality, Filinski, Abschnitte 2.2.1 Products and coproducts, 2.2.2 Terminal and initial objects, 2.5.2 CBV with lazy products, and 2.5.3 CBN with eager coproducts.
4 Stimmen
Hier gibt es einige gute Antworten. Eine interessante Sache, die nicht vollständig geklärt wurde, ist, dass deklarativ et Imperativ sind komplementär und symbiotisch, mehr als nur unterschiedliche Stile oder was vs. wie .
1 Stimmen
@Kit Imo, einige der Antworten auf dieser Seite verwechseln die Begriffe. DP == referentielle Transparenz (RT). DP & IP sind Gegensätze, also afaics nicht Ergänzungen eines Ganzen, d.h. ein ganzes Programm kann in beiden Stilen geschrieben werden. Der Aufruf einer Funktion kann entweder DP (RT) oder IP sein, ihre Implementierung kann beides sein oder eine Mischung daraus. Sie sind nicht symbiotisch in dem Sinne, dass ein Aufruf einer IP-Funktion in einer ansonsten DP-Funktion den Aufruf der DP-Funktion zu IP machen kann. Sie sind symbiotisch in dem Sinne, dass reale (z. B. funktional reaktive) Programme eine Mischung verwenden können, z. B. IP-Aufrufe auf oberster Ebene in DP-Funktionen.
1 Stimmen
Sollte in das Wiki aufgenommen werden oder ein Link auf etwas Ähnliches wie das Wiki usw. Hier ist ein toller Link in Wikipedia de.wikipedia.org/wiki/Vergleich_von_Programmierparadigmen
0 Stimmen
Upvote für jQuery meta.stackexchange.com/a/19492
1 Stimmen
Diese Frage wird in Meta diskutiert: meta.stackoverflow.com/q/342784/2751851