Den Unterschied zwischen prozedural und funktional wirklich verstehen

114

Es fällt mir wirklich schwer, den Unterschied zwischen prozeduralen und funktionalen Programmierparadigmen zu verstehen .

Hier sind die ersten beiden Absätze aus dem Wikipedia-Eintrag zur funktionalen Programmierung :

In der Informatik ist funktionale Programmierung ein Programmierparadigma, das Berechnung als Bewertung mathematischer Funktionen behandelt und Zustands- und veränderbare Daten vermeidet. Es betont die Anwendung von Funktionen im Gegensatz zum imperativen Programmierstil, der Zustandsänderungen betont. Die funktionale Programmierung hat ihre Wurzeln in der Lambda-Rechnung, einem formalen System, das in den 1930er Jahren entwickelt wurde, um Funktionsdefinition, Funktionsanwendung und Rekursion zu untersuchen. Viele funktionale Programmiersprachen können als Ausarbeitungen des Lambda-Kalküls angesehen werden.

In der Praxis besteht der Unterschied zwischen einer mathematischen Funktion und dem Begriff einer "Funktion", die in der imperativen Programmierung verwendet wird, darin, dass imperative Funktionen Nebenwirkungen haben können, die den Wert des Programmzustands ändern. Aus diesem Grund fehlt ihnen die referenzielle Transparenz, dh der gleiche Sprachausdruck kann je nach Status des ausführenden Programms zu unterschiedlichen Zeiten zu unterschiedlichen Werten führen. Umgekehrt hängt der Ausgabewert einer Funktion im Funktionscode nur von den Argumenten ab, die in die Funktion eingegeben werden. Wenn Sie also eine Funktion fzweimal mit demselben Wert für ein Argument aufrufen x, erhalten Sie dasselbe Ergebnisf(x)beide Male. Die Beseitigung von Nebenwirkungen kann das Verständnis und die Vorhersage des Verhaltens eines Programms erheblich erleichtern. Dies ist eine der Hauptmotive für die Entwicklung der funktionalen Programmierung.

In Absatz 2 heißt es

Umgekehrt hängt der Ausgabewert einer Funktion im Funktionscode nur von den Argumenten ab, die in die Funktion eingegeben werden. Wenn Sie also eine Funktion fzweimal mit demselben Wert für ein Argument aufrufen, erhalten Sie beide Male xdasselbe Ergebnis f(x).

Ist das nicht genau der gleiche Fall für die prozedurale Programmierung?

Worauf sollte man bei prozeduralen und funktionalen Merkmalen achten?

Philosoph
quelle
1
Der Link "Charming Python: Funktionale Programmierung in Python" von Abafei wurde unterbrochen. Hier ist eine gute Reihe von Links: ibm.com/developerworks/linux/library/l-prog/index.html ibm.com/developerworks/linux/library/l-prog2/index.html
Chris Koknat
Ein weiterer Aspekt davon ist die Benennung. Z.B. In JavaScript und Common Lisp verwenden wir den Begriff Funktion, obwohl sie Nebenwirkungen zulassen, und in Schema wird dies konsequent als prozeduere bezeichnet. Eine reine CL-Funktion kann als reine Funktionsschema-Prozedur geschrieben werden. In fast allen Büchern über Scheme wird der Begriff "Verfahren" verwendet, da es sich um das im Standard verwendete Tyerm handelt und es nichts damit zu tun hat, dass es prozedural oder funktional ist.
Sylwester

Antworten:

276

Funktionale Programmierung

Funktionale Programmierung bezieht sich auf die Fähigkeit, Funktionen als Werte zu behandeln.

Betrachten wir eine Analogie mit "regulären" Werten. Wir können zwei ganzzahlige Werte nehmen und sie mit dem +Operator kombinieren , um eine neue ganze Zahl zu erhalten. Oder wir können eine Ganzzahl mit einer Gleitkommazahl multiplizieren, um eine Gleitkommazahl zu erhalten.

In der funktionalen Programmierung können wir zwei Funktionswerte kombinieren, um mithilfe von Operatoren wie Compose oder Lift einen neuen Funktionswert zu erzeugen . Oder wir können einen Funktionswert und einen Datenwert kombinieren, um mithilfe von Operatoren wie map oder fold einen neuen Datenwert zu erzeugen .

Beachten Sie, dass viele Sprachen über funktionale Programmierfunktionen verfügen - auch über Sprachen, die normalerweise nicht als funktionale Sprachen angesehen werden. Sogar Großvater FORTRAN unterstützte Funktionswerte, obwohl es nicht viel an Funktionskombinationsoperatoren bot. Damit eine Sprache als "funktional" bezeichnet werden kann, müssen funktionale Programmierfunktionen in großem Umfang berücksichtigt werden.

Verfahrensprogrammierung

Prozedurale Programmierung bezieht sich auf die Fähigkeit, eine gemeinsame Folge von Anweisungen in eine Prozedur zu kapseln, so dass diese Anweisungen von vielen Stellen aus aufgerufen werden können, ohne auf Kopieren und Einfügen zurückgreifen zu müssen. Da Prozeduren eine sehr frühe Entwicklung in der Programmierung waren, ist die Fähigkeit fast immer mit dem Programmierstil verbunden, der von der maschinen- oder montagesprachlichen Programmierung gefordert wird: Ein Stil, der den Begriff der Speicherorte und Anweisungen betont, die Daten zwischen diesen Orten verschieben.

Kontrast

Die beiden Stile sind keine wirklichen Gegensätze - sie unterscheiden sich nur voneinander. Es gibt Sprachen, die beide Stile vollständig unterstützen (z. B. LISP). Das folgende Szenario vermittelt möglicherweise einen Eindruck von einigen Unterschieden zwischen den beiden Stilen. Schreiben wir einen Code für eine Unsinnanforderung, in der wir feststellen möchten, ob alle Wörter in einer Liste eine ungerade Anzahl von Zeichen haben. Erstens, prozeduraler Stil:

function allOdd(words) {
  var result = true;
  for (var i = 0; i < length(words); ++i) {
    var len = length(words[i]);
    if (!odd(len)) {
      result = false;
      break;
    }
  }
  return result;
}

Ich gehe davon aus, dass dieses Beispiel verständlich ist. Nun, funktionaler Stil:

function allOdd(words) {
  return apply(and, map(compose(odd, length), words));
}

Diese Definition funktioniert von innen nach außen und bewirkt Folgendes:

  1. compose(odd, length)kombiniert die Funktionen oddund length, um eine neue Funktion zu erstellen, die bestimmt, ob die Länge eines Strings ungerade ist.
  2. map(..., words)ruft diese neue Funktion für jedes Element in auf wordsund gibt schließlich eine neue Liste von Booleschen Werten zurück, die jeweils angeben, ob das entsprechende Wort eine ungerade Anzahl von Zeichen hat.
  3. apply(and, ...)Wendet den Operator "und" auf die resultierende Liste an und -ing alle Booleschen Werte zusammen, um das Endergebnis zu erhalten.

Sie können diesen Beispielen entnehmen, dass es bei der prozeduralen Programmierung sehr darum geht, Werte in Variablen zu verschieben und die Operationen, die zur Erzeugung des Endergebnisses erforderlich sind, explizit zu beschreiben. Im Gegensatz dazu betont der Funktionsstil die Kombination von Funktionen, die erforderlich sind, um die anfängliche Eingabe in die endgültige Ausgabe umzuwandeln.

Das Beispiel zeigt auch die typischen relativen Größen von prozeduralem gegenüber funktionalem Code. Darüber hinaus wird gezeigt, dass die Leistungsmerkmale von Verfahrenscode möglicherweise leichter zu erkennen sind als die von Funktionscode. Bedenken Sie: Berechnen die Funktionen die Länge aller Wörter in der Liste oder stoppt jedes Wort unmittelbar nach dem Auffinden des ersten Wortes mit gerader Länge? Andererseits ermöglicht der Funktionscode einer qualitativ hochwertigen Implementierung eine ziemlich ernsthafte Optimierung, da er in erster Linie Absicht und nicht einen expliziten Algorithmus ausdrückt.

Weiterführende Literatur

Diese Frage taucht häufig auf ... siehe zum Beispiel:

In John Backus 'Vortrag zum Turing-Preis werden die Motivationen für die funktionale Programmierung ausführlich dargelegt:

Kann die Programmierung vom von Neumann-Stil befreit werden?

Ich sollte dieses Papier im gegenwärtigen Kontext wirklich nicht erwähnen, weil es ziemlich technisch wird, ziemlich schnell. Ich konnte einfach nicht widerstehen, weil ich denke, dass es wirklich grundlegend ist.


Nachtrag - 2013

Kommentatoren weisen darauf hin, dass populäre zeitgenössische Sprachen andere Programmierstile als prozedural und funktional anbieten. Solche Sprachen bieten häufig einen oder mehrere der folgenden Programmierstile:

  • Abfrage (zB Listenverständnis, sprachintegrierte Abfrage)
  • Datenfluss (z. B. implizite Iteration, Massenoperationen)
  • objektorientiert (zB gekapselte Daten und Methoden)
  • sprachorientiert (zB anwendungsspezifische Syntax, Makros)

In den Kommentaren unten finden Sie Beispiele dafür, wie die Pseudocode-Beispiele in dieser Antwort von einigen der Funktionen dieser anderen Stile profitieren können. Insbesondere wird das Verfahrensbeispiel von der Anwendung praktisch jedes übergeordneten Konstrukts profitieren.

Die ausgestellten Beispiele vermeiden bewusst das Vermischen dieser anderen Programmierstile, um die Unterscheidung zwischen den beiden diskutierten Stilen hervorzuheben.

WReach
quelle
1
In der Tat eine nette Antwort, aber könnten Sie den Code ein wenig vereinfachen, zum Beispiel: "function allOdd (words) {foreach (automatisches Wort in Wörtern) {odd (Länge (Wort)? Return false :;} return true;}"
Dainius
Der dortige Funktionsstil ist im Vergleich zum "Funktionsstil" in Python ziemlich schwer zu lesen: def odd_words (words): return [x für x in Wörtern, wenn ungerade (len (x))]
Boxed
@boxed: Ihre odd_words(words)Definition unterscheidet sich von der der Antwort allOdd. Zum Filtern und Zuordnen werden häufig Listenverständnisse bevorzugt, aber hier soll die Funktion allOddeine Liste von Wörtern auf einen einzigen booleschen Wert reduzieren.
ShinNoNoir
@WReach: Ich hätte Ihr Funktionsbeispiel so geschrieben: function allOdd (Wörter) {return und (ungerade (Länge (erste (Wörter))), allOdd (Rest (Wörter))); } Es ist nicht eleganter als Ihr Beispiel, aber in einer schwanzrekursiven Sprache hätte es die gleichen Leistungsmerkmale wie der imperative Stil.
Mischoo
@mishoo Die Sprache muss sowohl rekursiv als auch streng und kurz und kurzgeschlossen sein, damit Ihre Annahme zutrifft, glaube ich.
kqr
46

Der wirkliche Unterschied zwischen funktionaler und imperativer Programmierung besteht in der Denkweise - imperative Programmierer denken an Variablen und Speicherblöcke, während funktionale Programmierer denken: "Wie kann ich meine Eingabedaten in meine Ausgabedaten umwandeln ?" - Ihr "Programm" ist die Pipeline und eine Reihe von Transformationen für die Daten , um sie von der Eingabe zur Ausgabe zu übertragen. Das ist der interessante Teil IMO, nicht das Bit "Du sollst keine Variablen verwenden".

Infolge dieser Denkweise beschreiben FP-Programme in der Regel, was passieren wird, und nicht den spezifischen Mechanismus, wie es passieren wird. Dies ist sehr effektiv, denn wenn wir klar angeben können, was "Auswählen" und "Wo" und "Aggregieren" bedeuten, sind wir es Sie können ihre Implementierungen austauschen, genau wie wir es mit AsParallel () tun, und plötzlich skaliert unsere Single-Threaded-App auf n Kerne.

Ana Betts
quelle
Wie können Sie die beiden anhand von Codebeispiel-Snippets gegenüberstellen? wirklich schätzen
Philoxopher
1
@ KerxPhilo: Hier ist eine sehr einfache Aufgabe (Zahlen von 1 bis n hinzufügen). Imperativ: Aktuelle Nummer ändern, Summe bisher ändern. Code: int i, sum; Summe = 0; für (i = 1; i <= n; i ++) {summe + = i; }. Funktional (Haskell): Nehmen Sie eine faule Liste von Zahlen, falten Sie sie zusammen und addieren Sie sie zu Null. Code: foldl (+) 0 [1..n]. Sorry, keine Formatierung in Kommentaren.
Dirkt
+1 auf die Antwort. Mit anderen Worten, bei der funktionalen Programmierung geht es darum, Funktionen ohne Nebenwirkungen zu schreiben, wann immer dies möglich ist, dh die Funktion gibt immer dasselbe zurück, wenn dieselben Parameter angegeben werden - das ist die Grundlage. Wenn Sie diesem Ansatz bis zum Äußersten folgen, werden Ihre Nebenwirkungen (Sie brauchen sie immer) isoliert und die restlichen Funktionen wandeln einfach Eingabedaten in Ausgabedaten um.
Beluchin
12
     Isn't that the same exact case for procedural programming?

Nein, da Verfahrenscode Nebenwirkungen haben kann. Beispielsweise kann der Status zwischen Anrufen gespeichert werden.

Es ist jedoch möglich, Code zu schreiben, der diese Einschränkung in prozeduralen Sprachen erfüllt. Es ist auch möglich, Code zu schreiben, der diese Einschränkung in einigen als funktionsfähig geltenden Sprachen verletzt.

Andy Thomas
quelle
1
Können Sie ein Beispiel und einen Vergleich zeigen? Wirklich zu schätzen, wenn Sie könnten.
Philosoph
8
Die Funktion rand () in C liefert für jeden Aufruf ein anderes Ergebnis. Es speichert den Status zwischen Anrufen. Es ist nicht referenziell transparent. Im Vergleich dazu gibt std :: max (a, b) in C ++ bei gleichen Argumenten immer das gleiche Ergebnis zurück und hat keine Nebenwirkungen (von denen ich weiß ...).
Andy Thomas
11

Ich bin mit WReachs Antwort nicht einverstanden. Lassen Sie uns seine Antwort ein wenig dekonstruieren, um zu sehen, woher die Meinungsverschiedenheit kommt.

Erstens sein Code:

function allOdd(words) {
  var result = true;
  for (var i = 0; i < length(words); ++i) {
    var len = length(words[i]);
    if (!odd(len)) {
      result = false;
      break;
    }
  }
  return result;
}

und

function allOdd(words) {
  return apply(and, map(compose(odd, length), words));
}

Das erste, was zu beachten ist, ist, dass er zusammenwächst:

  • Funktionell
  • Ausdrucksorientiert und
  • Iteratorzentriert

Programmierung und das Fehlen der Fähigkeit zur iterativen Stilprogrammierung, einen expliziteren Kontrollfluss als ein typischer Funktionsstil zu haben.

Lassen Sie uns schnell darüber sprechen.

Ausdrucksorientierter Stil ist ein Stil, bei dem Dinge so weit wie möglich zu Dingen bewertet werden . Obwohl funktionale Sprachen für ihre Liebe zu Ausdrücken berühmt sind, ist es tatsächlich möglich, eine funktionale Sprache ohne zusammensetzbare Ausdrücke zu haben. Ich werde einen erfinden, bei dem es keine Ausdrücke gibt, nur Aussagen.

lengths: map words length
each_odd: map lengths odd
all_odd: reduce each_odd and

Dies ist so ziemlich das Gleiche wie zuvor, außer dass Funktionen nur durch Ketten von Anweisungen und Bindungen verkettet werden.

Ein iteratorzentrierter Programmierstil könnte von Python übernommen werden. Verwenden wir einen rein iterativen, iteratorzentrierten Stil:

def all_odd(words):
    lengths = (len(word) for word in words)
    each_odd = (odd(length) for length in lengths)
    return all(each_odd)

Dies ist nicht funktionsfähig, da jede Klausel ein iterativer Prozess ist und sie durch explizite Pause und Wiederaufnahme der Stapelrahmen miteinander verbunden sind. Die Syntax kann teilweise von einer funktionalen Sprache inspiriert sein, wird jedoch auf eine vollständig iterative Ausführungsform davon angewendet.

Natürlich können Sie dies komprimieren:

def all_odd(words):
    return all(odd(len(word)) for word in words)

Imperativ sieht jetzt nicht so schlecht aus, oder? :) :)

Der letzte Punkt betraf einen expliziteren Kontrollfluss. Schreiben wir den Originalcode neu, um dies zu nutzen:

function allOdd(words) {
    for (var i = 0; i < length(words); ++i) {
        if (!odd(length(words[i]))) {
            return false;
        }
    }
    return true;
}

Mit Iteratoren können Sie haben:

function allOdd(words) {
    for (word : words) { if (!odd(length(word))) { return false; } }
    return true;
}

Was ist der Sinn einer funktionalen Sprache, wenn der Unterschied zwischen:

return all(odd(len(word)) for word in words)
return apply(and, map(compose(odd, length), words))
for (word : words) { if (!odd(length(word))) { return false; } }
return true;


Das Hauptmerkmal einer funktionalen Programmiersprache besteht darin, dass sie Mutationen als Teil des typischen Programmiermodells entfernt. Menschen verstehen dies oft so, dass eine funktionale Programmiersprache keine Anweisungen enthält oder Ausdrücke verwendet, dies sind jedoch Vereinfachungen. Eine funktionale Sprache ersetzt die explizite Berechnung durch eine Verhaltenserklärung, für die die Sprache dann eine Reduzierung durchführt.

Wenn Sie sich auf diese Teilmenge der Funktionen beschränken, können Sie mehr Garantien für das Verhalten Ihrer Programme haben und diese freier erstellen.

Wenn Sie eine funktionale Sprache haben, ist das Erstellen neuer Funktionen im Allgemeinen so einfach wie das Erstellen eng verwandter Funktionen.

all = partial(apply, and)

Dies ist nicht einfach oder vielleicht sogar nicht möglich, wenn Sie die globalen Abhängigkeiten einer Funktion nicht explizit gesteuert haben. Das Beste an der funktionalen Programmierung ist, dass Sie konsistentere generische Abstraktionen erstellen und darauf vertrauen können, dass sie zu einem größeren Ganzen kombiniert werden können.

Veedrac
quelle
Weißt du, ich bin mir ziemlich sicher, dass ein applyVorgang nicht ganz der gleiche ist wie ein foldoder reduce, obwohl ich der guten Fähigkeit zustimme, sehr allgemeine Algorithmen zu haben.
Benedict Lee
Ich habe noch nie davon gehört apply, foldoder reduce, aber es sieht für mich so aus, als müsste es in diesem Zusammenhang sein, damit es einen Booleschen Wert zurückgibt.
Veedrac
Ah, ok, ich war durch die Benennung verwirrt. Danke, dass du es geklärt hast.
Benedict Lee
6

Im prozeduralen Paradigma (soll ich stattdessen "strukturierte Programmierung" sagen?) Haben Sie veränderlichen Speicher und Anweisungen geteilt, die ihn in einer bestimmten Reihenfolge (nacheinander) lesen / schreiben.

Im Funktionsparadigma haben Sie Variablen und Funktionen (im mathematischen Sinne: Variablen variieren nicht über die Zeit, Funktionen können nur etwas basierend auf ihren Eingaben berechnen).

(Dies ist zu stark vereinfacht, z. B. verfügen FPLs normalerweise über Funktionen zum Arbeiten mit veränderlichem Speicher, während prozedurale Sprachen häufig Verfahren höherer Ordnung unterstützen, sodass die Dinge nicht so eindeutig sind. Dies sollte Ihnen jedoch eine Idee geben.)

Artyom Shalkhakov
quelle
2

Das charmante Python: Die funktionale Programmierung in Python von IBM Developerworks hat mir wirklich geholfen, den Unterschied zu verstehen.

Insbesondere für jemanden, der Python ein wenig kennt, können die Codebeispiele in diesem Artikel, in denen verschiedene Dinge funktional und prozedural kontrastiert werden, den Unterschied zwischen prozeduraler und funktionaler Programmierung verdeutlichen.

Abbafei
quelle
2

In der funktionalen Programmierung müssen Sie nur zwei Dinge wissen, um über die Bedeutung eines Symbols (Variablen- oder Funktionsname) nachzudenken - den aktuellen Umfang und den Namen des Symbols. Wenn Sie eine rein funktionale Sprache mit Unveränderlichkeit haben, handelt es sich bei beiden Konzepten um "statische" Konzepte (Entschuldigung für stark überladene Namen). Dies bedeutet, dass Sie beide - den aktuellen Bereich und den Namen - nur anhand des Quellcodes anzeigen können.

Wenn Sie in der prozeduralen Programmierung die Frage beantworten möchten, was der Wert dahinter ist, müssen xSie auch wissen, wie Sie dorthin gekommen sind. Umfang und Name allein reichen nicht aus. Und dies ist das, was ich als die größte Herausforderung ansehen würde, da dieser Ausführungspfad eine "Laufzeit" -Eigenschaft ist und von so vielen verschiedenen Dingen abhängen kann, dass die meisten Leute lernen, ihn nur zu debuggen und nicht zu versuchen, den Ausführungspfad wiederherzustellen.

vasily
quelle
1

Ich habe kürzlich über den Unterschied in Bezug auf das Ausdrucksproblem nachgedacht . Phil Wadlers Beschreibung wird oft zitiert, aber die akzeptierte Antwort auf diese Frage ist wahrscheinlich leichter zu befolgen. Grundsätzlich scheint es, dass imperative Sprachen dazu neigen, einen Ansatz für das Problem zu wählen, während funktionale Sprachen dazu neigen, den anderen zu wählen.

John L.
quelle
0

Ein klarer Unterschied zwischen den beiden Programmierparadigmen ist der Zustand.

In der funktionalen Programmierung wird der Status vermieden. Einfach ausgedrückt, es wird keine Variable geben, der ein Wert zugewiesen ist.

Beispiel:

def double(x):
    return x * 2

def doubleLst(lst):
    return list(map(double, action))

Die prozedurale Programmierung verwendet jedoch den Status.

Beispiel:

def doubleLst(lst):
    for i in range(len(lst)):
        lst[i] = lst[i] * 2  # assigning of value i.e. mutation of state
    return lst
Tox
quelle