Funktionale Programmierung und nicht funktionale Programmierung


71

In meinem zweiten Studienjahr wurde uns Haskell "beigebracht", ich weiß fast nichts darüber und noch weniger über funktionale Programmierung.

Was ist funktionale Programmierung, warum und / oder wo möchte ich sie anstelle von nicht funktionaler Programmierung verwenden und bin ich zu Recht der Meinung, dass C eine nicht funktionale Programmiersprache ist?

Antworten:


90

Ein wesentliches Merkmal einer funktionalen Sprache ist das Konzept erstklassiger Funktionen. Die Idee ist, dass Sie Funktionen als Parameter an andere Funktionen übergeben und als Werte zurückgeben können.

Bei der funktionalen Programmierung wird Code geschrieben, der den Status nicht ändert. Der Hauptgrund dafür ist, dass aufeinanderfolgende Aufrufe einer Funktion das gleiche Ergebnis liefern. Sie können Funktionscode in jeder Sprache schreiben, die erstklassige Funktionen unterstützt. Es gibt jedoch einige Sprachen wie Haskell, in denen Sie den Status nicht ändern können. Tatsächlich sollten Sie überhaupt keine Nebenwirkungen (wie das Ausdrucken von Text) verursachen - was so klingt, als ob es völlig nutzlos sein könnte.

Haskell verwendet stattdessen einen anderen Ansatz für IO: Monaden. Dies sind Objekte, die die gewünschte E / A-Operation enthalten, die von der obersten Ebene Ihres Interpreters ausgeführt werden soll. Auf jeder anderen Ebene sind sie einfach Objekte im System.

Welche Vorteile bietet die funktionale Programmierung? Die funktionale Programmierung ermöglicht die Codierung mit weniger Fehlerpotentialen, da jede Komponente vollständig isoliert ist. Die Verwendung von Rekursions- und erstklassigen Funktionen ermöglicht auch einfache Korrektheitsnachweise, die typischerweise die Struktur des Codes widerspiegeln.


7
"Der Hauptgrund dafür ist, dass aufeinanderfolgende Aufrufe einer Funktion das gleiche Ergebnis liefern"Why do we want successive calls to a function to yield the same result. What is the problem with the way things are in C? I never understood this.
Lazer

16
@Lazer Es gibt einige Gründe, warum das unerwünscht ist. Wenn eine Funktion dasselbe Ergebnis zurückgibt, können Sie dieses Ergebnis zwischenspeichern und zurückgeben, wenn die Funktion erneut aufgerufen wird. Zweitens, wenn die Funktion nicht das gleiche Ergebnis zurückgibt, bedeutet dies, dass sie sich auf einen externen Status des Programms stützt und die Funktion daher nicht einfach vorberechnet oder parallelisiert werden kann.
Kyle Cronin

9
Dies ist auch ein großer Vorteil beim Analysieren und Debuggen von Code. Nebenwirkungen sind im Grunde nur implizite Eingaben in die Funktion, die dem Programmierer verborgen bleibt.
Magnus Kronqvist

@MagnusKronqvist Wenn sie veränderbar sind, sind sie nicht mit impliziten Eingaben identisch, da sie beschrieben werden können. Eingabeargumente können normalerweise nicht beschrieben werden (es sei denn, sie sind Referenztypen). Die Bedeutung der Unterscheidung besteht darin, dass der Zugriff auf einen veränderlichen externen Zustand die Funktion zwingend und nicht deklarativ macht .
Shelby Moore III

"Sie können Funktionscode in jeder Sprache schreiben, die erstklassige Funktionen unterstützt" - Ich möchte hinzufügen, dass 1) die Unterstützung von Typklassen in der Sprache Sie vor vielen Wiederholungen bewahrt und 2) Java auch erstklassige Funktionen darstellen kann (mit Einzel-) Methodenklassenobjekte, siehe Guavas Funktion von FunctionalJavas F), aber die Syntax ist zu ausführlich, um verwendet werden zu können.
Ron

23

Was ist funktionale Programmierung?

Es gibt zwei verschiedene Definitionen von "funktionaler Programmierung", die heute allgemein verwendet werden:

Die ältere Definition (die von Lisp stammt) besagt, dass es bei der funktionalen Programmierung um die Programmierung mit erstklassigen Funktionen geht, dh Funktionen werden wie jeder andere Wert behandelt, sodass Sie Funktionen als Argumente an andere Funktionen übergeben und Funktionen Funktionen unter ihren Rückgabewerten zurückgeben können. Dies gipfelt in der Verwendung von Funktionen höherer Ordnung wie mapund reduce(Sie haben vielleicht von mapReduceeiner einzelnen Operation gehört, die häufig von Google verwendet wird, und es ist nicht überraschend, dass es sich um einen nahen Verwandten handelt!). Die .NET-Typen System.Funcund System.Actionstellen Funktionen höherer Ordnung in C # zur Verfügung. Obwohl das Currying in C # unpraktisch ist, sind Funktionen, die andere Funktionen als Argumente akzeptieren, häufig, z Parallel.For. B. die Funktion.

Die jüngere Definition (von Haskell populär gemacht) ist, dass es bei der funktionalen Programmierung auch darum geht, Nebenwirkungen einschließlich Mutationen zu minimieren und zu kontrollieren, dh Programme zu schreiben, die Probleme durch das Verfassen von Ausdrücken lösen. Dies wird häufiger als "rein funktionale Programmierung" bezeichnet. Dies wird durch völlig unterschiedliche Ansätze für Datenstrukturen ermöglicht, die als "rein funktionale Datenstrukturen" bezeichnet werden. Ein Problem besteht darin, dass die Übersetzung traditioneller imperativer Algorithmen zur Verwendung rein funktionaler Datenstrukturen die Leistung in der Regel um das 10-fache verschlechtert. Haskell ist die einzige überlebende rein funktionale Programmiersprache, aber die Konzepte haben sich in die Mainstream-Programmierung mit Bibliotheken wie Linqin .NET eingeschlichen.

Wo würde ich es anstelle einer nicht funktionierenden Programmierung verwenden wollen?

Überall. Lambdas in C # haben jetzt große Vorteile gezeigt. C ++ 11 hat Lambdas. Es gibt keine Entschuldigung, Funktionen höherer Ordnung jetzt nicht zu verwenden. Wenn Sie eine Sprache wie F # verwenden können, profitieren Sie auch von Typinferenz, automatischer Generalisierung, Currying und teilweiser Anwendung (sowie vielen anderen Sprachfunktionen!).

Habe ich Recht, wenn ich denke, dass C eine nicht funktionierende Programmiersprache ist?

Ja. C ist eine prozedurale Sprache. Sie können jedoch einige Vorteile der funktionalen Programmierung nutzen, indem Sie Funktionszeiger und void *in C verwenden.


Können Sie bitte näher auf diese Zeile eingehen oder ein Beispiel nennen: "Ein Problem besteht darin, dass die Übersetzung traditioneller imperativer Algorithmen zur Verwendung rein funktionaler Datenstrukturen die Leistung in der Regel um das 10-fache verschlechtert."
Jaguar

1
Fast alle herkömmlichen Algorithmen (z. B. Quicksort, LZW, LU-Zerlegung, Prims MST) sind fast immer von Natur aus unerlässlich. Insbesondere können sie niemals von der Persistenz profitieren: Sie arbeiten immer nur mit der neuesten Version einer Sammlung und verwenden alte Versionen niemals wieder. Dies macht sie ideal für die Implementierung mit herkömmlichen veränderlichen Sammlungen. Wenn Sie sie jedoch mit rein funktionalen Sammlungen implementieren, zahlen Sie den Preis für die Persistenz (dh langsame Geschwindigkeit), profitieren jedoch nicht von den Vorteilen.
JD

1
Stattdessen müssen Sie genauer nach exotischen Algorithmen suchen, die das gleiche Problem lösen und von der Persistenz profitieren, z. B. Merge Sort und Borůvkas MST.
JD

6

Vielleicht lohnt es sich, diesen Artikel über F # "101" auf CoDe Mag zu lesen, der kürzlich veröffentlicht wurde.

Außerdem hat Dustin Campbell einen großartigen Blog, in dem er viele Artikel über seine Abenteuer veröffentlicht hat, wie man sich mit F # vertraut macht.

Ich hoffe du findest diese nützlich :)

BEARBEITEN:

Um nur hinzuzufügen, mein Verständnis der funktionalen Programmierung ist, dass alles eine Funktion oder Parameter einer Funktion ist und nicht Instanzen / zustandsbehaftete Objekte. Aber ich könnte mich irren. F # ist etwas, worauf ich mich einlassen möchte, aber einfach nicht die Zeit haben! :) :)


4

Der Beispielcode von John the Statistician zeigt keine funktionale Programmierung an, da bei der funktionalen Programmierung der Schlüssel darin besteht, dass der Code KEINE ZUWEISUNGEN ausführt ( record = thingConstructor(t)ist eine Zuweisung) und KEINE NEBENWIRKUNGEN hat ( localMap.put(record)eine Anweisung mit Nebeneffekt). . Aufgrund dieser beiden Einschränkungen wird alles, was eine Funktion tut, vollständig von ihren Argumenten und ihrem Rückgabewert erfasst. Schreiben Sie den Code des Statistikers so um, wie er aussehen müsste, wenn Sie eine funktionale Sprache mit C ++ emulieren möchten:

RT getOrCreate (const T Sache, 
                  const Funktion <RT <T >> thingConstructor, 
                  const Map <T, RT <T> localMap) {.
    return localMap.contains (t)?
        localMap.get (t):
        localMap.put (t, thingConstructor (t));
}}

Aufgrund der Regel ohne Nebenwirkungen ist jede Anweisung Teil des Rückgabewerts (daher returnsteht sie an erster Stelle ), und jede Anweisung ist ein Ausdruck. In Sprachen, die die funktionale Programmierung erzwingen, ist das returnSchlüsselwort impliziert, und die if- Anweisung verhält sich wie der ?:Operator von C ++ .

Außerdem ist alles unveränderlich, daher localMap.putmuss eine neue Kopie von localMap erstellt und zurückgegeben werden, anstatt die ursprüngliche localMap wie bei einem normalen C ++ - oder Java-Programm zu ändern . Abhängig von der Struktur von localMap kann die Kopie Zeiger auf das Original wiederverwenden, wodurch die zu kopierende Datenmenge verringert wird.

Zu den Vorteilen der funktionalen Programmierung gehört die Tatsache, dass funktionale Programme kürzer sind und es einfacher ist, ein funktionales Programm zu ändern (da keine versteckten globalen Effekte zu berücksichtigen sind), und es einfacher ist, das Programm direkt in das Programm zu integrieren erster Platz.

Funktionsprogramme werden jedoch in der Regel langsam ausgeführt (aufgrund all der zu kopierenden Aufgaben) und sie interagieren nicht gut mit anderen Programmen, Betriebssystemprozessen oder Betriebssystemen, die sich mit Speicheradressen befassen, Little-Endian Byteblöcke und andere maschinenspezifische, nicht funktionierende Bits. Der Grad der Nichtinteroperabilität korreliert tendenziell umgekehrt mit dem Grad der funktionellen Reinheit und der Strenge des Typsystems.

Die populäreren funktionalen Sprachen haben wirklich sehr, sehr strenge Typsysteme. In OCAML können Sie nicht einmal Ganzzahl- und Gleitkomma-Mathematik mischen oder dieselben Operatoren verwenden (+ dient zum Hinzufügen von Ganzzahlen, +. Zum Hinzufügen von Gleitkommazahlen). Dies kann entweder ein Vorteil oder ein Nachteil sein, je nachdem, wie sehr Sie die Fähigkeit eines Typprüfers schätzen, bestimmte Arten von Fehlern zu erkennen.

Funktionale Sprachen haben in der Regel auch sehr große Laufzeitumgebungen. Haskell ist eine Ausnahme (ausführbare GHC-Programme sind sowohl zur Kompilierungszeit als auch zur Laufzeit fast so klein wie C-Programme), aber SML-, Common Lisp- und Scheme-Programme benötigen immer Tonnen Speicher.


1
Ich bin nicht der Meinung, dass funktionale Sprachen (und wie Erlang gelegentlich) einzelne Aufgaben zulassen können, die keine Nebenwirkungen haben. Ihr Standpunkt zu Nebenwirkungen ist jedoch gut aufgenommen. Ich werde meinen Code jedoch nicht ändern, da ich denke, dass es wichtiger ist, den Vorteil von Funktionsmerkmalen in einer hybriden Sprache zu veranschaulichen als eine strikte Reinheit.
John mit Waffel

1
Bei der überwiegenden Mehrheit der funktionalen Programmierung geht es um die Verwendung erstklassiger Funktionen und nicht um das Verbot von Nebenwirkungen. Meine Messungen zeigen, dass Ihre Aussagen über die Größe von Laufzeitumgebungen falsch sind: GHC erzeugt Binärdateien, die doppelt so groß sind wie OCaml und Standard ML (mit MLton) und 20 Mal so groß wie HLVM.
JD

1
Kombinieren Sie FP nicht mit Nebenwirkungen, dh mit DP vs. IP . Das ist reine FP.
Shelby Moore III

3

Ja, Sie denken zu Recht, dass C eine nicht funktionierende Sprache ist. C ist eine prozedurale Sprache.


3

Ich bevorzuge die funktionale Programmierung, um mir wiederholte Arbeit zu ersparen, indem ich eine abstraktere Version erstelle und diese stattdessen verwende. Lassen Sie mich ein Beispiel geben. In Java erstelle ich häufig Karten, um Strukturen aufzuzeichnen und damit getOrCreate-Strukturen zu schreiben.

SomeKindOfRecord<T> getOrCreate(T thing) { 
    if(localMap.contains(thing)) { return localMap.get(thing); }
    SomeKindOfRecord<T> record = new SomeKindOfRecord<T>(thing);
    localMap = localMap.put(thing, record);
    return record; 
}

Das kommt sehr oft vor. Jetzt konnte ich in einer funktionalen Sprache schreiben

RT<T> getOrCreate(T thing, 
                  Function<RT<T>> thingConstructor, 
                  Map<T,RT<T>> localMap) {
    if(localMap.contains(thing)) { return localMap.get(thing); }
    RT<T> record = thingConstructor(thing);
    localMap = localMap.put(thing,record);
    return record; 
}

und ich würde nie wieder ein neues davon schreiben müssen, ich könnte es erben. Aber ich könnte es besser machen als zu erben, könnte ich im Konstruktor dieser Sache sagen

getOrCreate = myLib.getOrCreate(*,
                                SomeKindOfRecord<T>.constructor(<T>), 
                                localMap);

(wobei * eine Art Notation "Diesen Parameter offen lassen" ist, die eine Art Currying ist)

und dann ist das lokale getOrCreate genau das gleiche wie es gewesen wäre, wenn ich das Ganze in einer Zeile ohne Vererbungsabhängigkeiten geschrieben hätte.


2

Wenn Sie nach einem guten Text auf F # suchen

Expert F # wird von Don Syme mitgeschrieben. Schöpfer von F #. Er arbeitete speziell an Generika in .NET, damit er F # erstellen konnte.

F # ist OCaml nachempfunden, sodass Sie mit jedem OCaml-Text auch F # lernen können.


Hat Don wirklich nur Generika für F # gemacht? Ich finde das verdächtig überraschend!
JD

1

Ich finde Was ist funktionale Programmierung? um nützlich zu sein

Bei der funktionalen Programmierung geht es darum, reine Funktionen zu schreiben und versteckte Ein- und Ausgänge so weit wie möglich zu entfernen, damit so viel Code wie möglich nur eine Beziehung zwischen Ein- und Ausgängen beschreibt.

Bevorzugen Sie explizite whenParameter

public Program getProgramAt(TVGuide guide, int channel, Date when) {
  Schedule schedule = guide.getSchedule(channel);

  Program program = schedule.programAt(when);

  return program;
}

Über

public Program getCurrentProgram(TVGuide guide, int channel) {
  Schedule schedule = guide.getSchedule(channel);

  Program current = schedule.programAt(new Date());

  return current;
}

Eine funktionale Sprache ist aktiv gegen Nebenwirkungen. Nebenwirkungen sind Komplexität und Komplexität sind Fehler und Fehler sind der Teufel. Eine funktionale Sprache hilft Ihnen auch dabei, Nebenwirkungen zu bekämpfen.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.