Fallstricke / Nachteile der funktionalen Programmierung [geschlossen]

70

Wann möchten Sie KEINE funktionale Programmierung verwenden? Was kann es nicht so gut?

Ich bin eher auf der Suche nach Nachteilen des gesamten Paradigmas, nicht nach Dingen wie "nicht weit verbreitet" oder "kein guter Debugger verfügbar". Diese Antworten mögen ab sofort richtig sein, aber sie befassen sich damit, dass FP ein neues Konzept ist (ein unvermeidbares Problem) und keine inhärenten Eigenschaften.

Verbunden:

Gordon Gustafson
quelle
6
"Fallstricke der objektorientierten Programmierung" ist nach 1800 Ansichten nicht CW. (nicht versuchen, unhöflich zu sein, sondern nur Fragen vergleichen. Vielleicht sollten beide CW .: D sein)
Gordon Gustafson
Dies hat einen subjektiven Tag, aber die Antworten, die ich bisher gesehen habe, waren eher objektiv. Ich könnte subjektive Tags entfernen.
Brian
Was ist mit endemischen Qualitäten?
JD

Antworten:

45

Es fällt mir schwer, an viele Nachteile der funktionalen Programmierung zu denken. Andererseits bin ich ein ehemaliger Vorsitzender der Internationalen Konferenz für funktionale Programmierung, also können Sie sicher annehmen, dass ich voreingenommen bin.

Ich denke, die Hauptnachteile haben mit Isolation und Eintrittsbarrieren zu tun. Das Lernen, gute funktionale Programme zu schreiben , bedeutet, zu lernen, anders zu denken, und es gut zu machen, erfordert einen erheblichen Aufwand an Zeit und Mühe . Ohne Lehrer ist es schwierig zu lernen. Diese Eigenschaften führen zu einigen Nachteilen:

  • Es ist wahrscheinlich, dass ein von einem Neuling geschriebenes Funktionsprogramm unnötig langsam ist - wahrscheinlicher als beispielsweise ein von einem Neuling in C geschriebenes C-Programm. Andererseits ist es ungefähr genauso wahrscheinlich, dass ein von einem Neuling geschriebenes C ++ - Programm wird unnötig langsam sein. (All diese glänzenden Eigenschaften ...)

    Im Allgemeinen haben Experten keine Schwierigkeiten, schnelle Funktionsprogramme zu schreiben. Tatsächlich sind einige der leistungsstärksten Parallelprogramme auf 8- und 16-Kern-Prozessoren jetzt in Haskell geschrieben .

  • Es ist wahrscheinlicher, dass jemand, der mit der funktionalen Programmierung beginnt, aufgibt, bevor er die versprochenen Produktivitätsgewinne realisiert, als jemand, der beispielsweise Python oder Visual Basic startet. Es gibt einfach nicht so viel Unterstützung in Form von Büchern und Entwicklungswerkzeugen.

  • Es gibt weniger Leute, mit denen man reden kann. Stackoverflow ist ein gutes Beispiel; relativ wenige Haskell-Programmierer besuchen die Site regelmäßig (obwohl ein Teil davon darin besteht, dass Haskell-Programmierer ihre eigenen lebhaften Foren haben, die viel älter und besser etabliert sind als Stackoverflow).

    Es ist auch wahr, dass Sie nicht so einfach mit Ihrem Nachbarn sprechen können, da funktionale Programmierkonzepte schwieriger zu lehren und zu lernen sind als die objektorientierten Konzepte hinter Sprachen wie Smalltalk, Ruby und C ++. Außerdem hat die objektorientierte Community jahrelang gute Erklärungen für ihre Arbeit entwickelt, während die Community für funktionale Programmierung zu denken scheint, dass ihre Inhalte offensichtlich großartig sind und keine speziellen Metaphern oder Vokabeln zur Erklärung benötigen. (Sie sind falsch. Ich warte immer noch auf das erste großartige Buch Functional Design Patterns .)

  • Ein bekannter Nachteil der verzögerten Funktionsprogrammierung (gilt für Haskell oder Clean, jedoch nicht für ML oder Scheme oder Clojure) ist, dass es sehr schwierig ist, die Zeit- und Raumkosten für die Bewertung eines verzögerten Funktionsprogramms vorherzusagen - selbst Experten können dies nicht es. Dieses Problem ist grundlegend für das Paradigma und verschwindet nicht. Es gibt ausgezeichnete Werkzeuge, um das Verhalten von Zeit und Raum post facto zu entdecken , aber um sie effektiv zu nutzen, müssen Sie bereits Experte sein.

Norman Ramsey
quelle
8
"Im Allgemeinen haben Experten keine Schwierigkeiten, schnelle Funktionsprogramme zu schreiben. Tatsächlich sind einige der leistungsstärksten Parallelprogramme auf 8- und 16-Kern-Prozessoren jetzt in Haskell geschrieben." Nur für die trivialsten Probleme. Siehe fliegendefrogblog.blogspot.com/2010/06/…
JD
5
@ Jon: Es hängt stark von der genauen Art des Problems ab und davon, welche Art von Cache-Lokalität Sie erhalten. Die Messung (von der Benchmarking nur ein Typ ist) zeigt, welcher am besten ist; Pontifikieren auf einer Webseite wird nicht.
Donal Fellows
1
@Donal: Die Berechnung der Cache-Komplexität Ihres Algorithmus unter Verwendung eines einfachen Multicore-Modells ist auch ein starker Indikator dafür, welcher am besten ist, wie ich auf dieser Webseite sagte.
JD
2
Ich könnte mir keinen besseren Thread vorstellen (naja, vielleicht einen oder zwei), der dem interessierten Leser hilft, zu einer eigenen Schlussfolgerung über die genauen Vorzüge (oder das Fehlen davon) von jdhs Meinungen zu gelangen.
sclv
8
Für die Aufzeichnung - mein Kommentar war eine Antwort auf einen jetzt gelöschten Kommentar von jdh, der auf einen ziemlich schmerzhaft zu lesenden reddit-Thread hinwies. Ich werde hier nicht weiter antworten, da mir die Tatsache nicht gefällt, dass jdh Kommentare löschen kann, ohne eine Spur anzugeben, und seine Bereitschaft dazu gezeigt hat.
sclv
29

Ein großer Nachteil der funktionalen Programmierung besteht darin, dass sie theoretisch nicht mit der Hardware und den meisten wichtigen Sprachen übereinstimmt. (Dies ist die Kehrseite einer seiner offensichtlichen Stärken: Sie können ausdrücken, was Sie tun möchten, anstatt wie der Computer es tun soll.)

Beispielsweise nutzt die funktionale Programmierung die Rekursion stark. Dies ist in der reinen Lambda-Rechnung in Ordnung, da der "Stapel" der Mathematik unbegrenzt ist. Natürlich ist der Stack auf echter Hardware sehr begrenzt. Das naive Rekursieren über einen großen Datensatz kann Ihr Programm zum Boom bringen. Die meisten funktionalen Sprachen optimieren die Schwanzrekursion so, dass dies nicht geschieht. Wenn Sie jedoch einen Algorithmus für die Schwanzrekursion verwenden, können Sie zu einer ziemlich unschönen Codegymnastik gezwungen werden (z. B. erstellt eine Schwanzrekursionskartenfunktion eine Rückwärtsliste oder muss einen Unterschied aufbauen Liste, daher muss zusätzliche Arbeit geleistet werden, um zu einer normalen zugeordneten Liste in der richtigen Reihenfolge im Vergleich zur nicht-rekursiven Version zurückzukehren.

(Danke an Jared Updike für den Vorschlag zur Differenzliste.)

Futter
quelle
9
Dies unterstreicht ein interessantes Problem mit FP: Um effektiv in FP zu programmieren, müssen Sie bestimmte Tricks kennen - insbesondere im Umgang mit Faulheit. In Ihrem Beispiel ist es tatsächlich einfach, Ihren Code-Schwanz rekursiv zu halten (unter Verwendung einer strengen Linksfalte) und zu vermeiden, dass Dinge in die Luft jagen. Sie haben die Liste nicht rückwärts erstellt und die Rückgabeliste umgekehrt. Der Trick besteht darin, Differenzlisten zu verwenden: en.wikipedia.org/wiki/Difference_list . Viele dieser Tricks sind nicht so einfach selbst herauszufinden. Zum Glück ist die Haskell-Community super freundlich (IRC-Kanal, Mailinglisten).
Jared Updike
4
Danke, Jared. Gute Infos. Zur Verteidigung meiner Beschreibung macht die OCaml-Standardbibliothek es jedoch so, wie ich es gesagt habe (stapelbegrenzt mapund schwanzrekursiv rev_map).
Chuck
23

Wenn Ihre Sprache keine guten Mechanismen bietet, um das Status- / Ausnahmeverhalten über Ihr Programm auszuloten (z. B. Syntaxzucker für monadische Bindungen), wird jede Aufgabe, die Status / Ausnahmen umfasst, zur Pflicht. (Selbst mit diesen Zuckern fällt es einigen Menschen möglicherweise schwerer, mit Zuständen / Ausnahmen in FP umzugehen.)

Funktionale Redewendungen führen häufig zu einer starken Umkehrung der Kontrolle oder Faulheit, was sich häufig negativ auf das Debuggen auswirkt (mithilfe eines Debuggers). (Dies wird etwas dadurch ausgeglichen, dass FP aufgrund von Unveränderlichkeit / referenzieller Transparenz viel weniger fehleranfällig ist, was bedeutet, dass Sie weniger häufig debuggen müssen.)

Brian
quelle
5
"Unveränderlichkeit / referenzielle Transparenz, was bedeutet, dass Sie weniger häufig debuggen müssen" ... und da alles aus kleinen unabhängigen Funktionen besteht, können Sie diese einfach direkt testen. Wenn jede Funktion (a) eine korrekte kleine Funktion oder (b) eine korrekte Zusammensetzung von zwei oder mehr korrekten kleinen Funktionen ist, dann wham! Ihr Programm ist korrekt.
Jared Updike
13

Philip Wadler schrieb einen Artikel darüber (Warum niemand funktionale Programmiersprachen verwendet) und ging auf die praktischen Fallstricke ein, die Menschen davon abhalten, FP-Sprachen zu verwenden:

Update: Unzugänglicher alter Link für Benutzer mit ACM-Zugriff:

Jared Updike
quelle
9
Bitte posten Sie den entsprechenden Text der Artikel. : D
Gordon Gustafson
@ CrazyJugglerDrummer: Ich denke, dass der ganze Artikel darüber handelt ;-)
Hynek -Pichi- Vychodil
1
Ich weiß, aber ich würde es lieber irgendwie sehen können, ohne es herunterzuladen und zu öffnen. Ist das möglich?
Gordon Gustafson
2
Entschuldigung für den unzugänglichen Link. Ich würde HTML-Text posten, aber PS / PDF ist eigentlich ein Bild und ich habe keine OCR-Software zur Hand. Ich könnte wohl irgendwo ein PDF davon posten. Ich bin mir nicht sicher, warum ACM einige dieser älteren Artikel versteckt. wollen sie diese Informationen nicht verbreiten?
Jared Updike
2
Online-Konverter für Postscript-Dateien :-D view.samurajdata.se/… !
Pavel Savara
8

Abgesehen von Geschwindigkeits- oder Adoptionsproblemen und der Behebung eines grundlegenderen Problems habe ich gehört, dass es mit funktionaler Programmierung sehr einfach ist, neue Funktionen für vorhandene Datentypen hinzuzufügen, aber es ist "schwierig", neue Datentypen hinzuzufügen. Erwägen:

(Geschrieben in SMLnj. Bitte entschuldigen Sie auch das etwas erfundene Beispiel.)

datatype Animal = Dog | Cat;

fun happyNoise(Dog) = "pant pant"
  | happyNoise(Cat) = "purrrr";

fun excitedNoise(Dog) = "bark!"
  | excitedNoise(Cat) = "meow!";

Ich kann sehr schnell Folgendes hinzufügen:

fun angryNoise(Dog) = "grrrrrr"
  | angryNoise(Cat) = "hisssss";

Wenn ich jedoch einen neuen Typ zu Animal hinzufüge, muss ich jede Funktion durchgehen, um Unterstützung dafür hinzuzufügen:

datatype Animal = Dog | Cat | Chicken;

fun happyNoise(Dog) = "pant pant"
  | happyNoise(Cat) = "purrrr"
  | happyNoise(Chicken) = "cluck cluck";

fun excitedNoise(Dog) = "bark!"
  | excitedNoise(Cat) = "meow!"
  | excitedNoise(Chicken) = "cock-a-doodle-doo!";

fun angryNoise(Dog) = "grrrrrr"
  | angryNoise(Cat) = "hisssss"
  | angryNoise(Chicken) = "squaaaawk!";

Beachten Sie jedoch, dass für objektorientierte Sprachen genau das Gegenteil der Fall ist. Es ist sehr einfach, einer abstrakten Klasse eine neue Unterklasse hinzuzufügen, aber es kann mühsam sein, wenn Sie der abstrakten Klasse / Schnittstelle eine neue abstrakte Methode hinzufügen möchten, damit alle Unterklassen implementiert werden können.

Ben Torell
quelle
9
Wenn Sie diese als Unterklassen einer abstrakten Klasse in einem OO implementiert haben, müssen Sie auch alle diese neuen Funktionen schreiben. Der einzige Unterschied besteht darin, wie Sie die Funktionen organisieren (nach Typ oder Verhalten).
Chuck
10
Dies wurde von niemand anderem als Philip Wadler als Ausdrucksproblem bezeichnet .
Jörg W Mittag
3
Wadler nennt dies das Ausdrucksproblem: en.wikipedia.org/wiki/Expression_Problem
Jared Updike
1
Was Sie haben, sind algebraische Datentypen - Sie gelten als geschlossen, aber erweiterbar! Wenn Sie Erweiterbarkeit wünschen, benötigen Sie Vererbung oder Typklassen / Existentials.
Dario
1
Dies hat nichts mit funktionaler Programmierung zu tun. Standard ML, F # und Haskell sind von diesem Problem betroffen. Mathematica, OCaml und Clojure sind es nicht.
JD
3

Ich wollte nur mit einer Anekdote anfangen, weil ich gerade Haskell lerne, während wir sprechen. Ich lerne Haskell, weil mir die Idee gefällt, Funktionen von Aktionen zu trennen, und es gibt einige wirklich sexy Theorien hinter der impliziten Parallelisierung, weil reine Funktionen von nicht reinen Funktionen isoliert werden.

Ich lerne jetzt seit drei Tagen die Fold-Funktionsklasse. Fold scheint eine sehr einfache Anwendung zu haben: eine Liste zu nehmen und sie auf einen einzigen Wert zu reduzieren. Haskell implementiert a foldlund foldrdafür. Die beiden Funktionen haben sehr unterschiedliche Implementierungen. Es gibt eine alternative Implementierung von foldl, genannt foldl'. Darüber hinaus gibt es eine Version mit einer etwas anderen Syntax foldr1und foldl1unterschiedlichen Anfangswerten. Davon gibt es eine entsprechende Implementierung von foldl1'for foldl1. Als ob all dies nicht umwerfend wäre, funktionieren die Funktionenfold[lr].*erfordern als Argumente und verwenden intern in der Reduktion haben zwei separate Signaturen, nur eine Variante arbeitet auf unendlichen Listen (r), und nur eine von ihnen wird im konstanten Speicher ausgeführt (wie ich verstehe (L), weil nur es erfordert a redex). foldrUm zu verstehen, warum mit unendlichen Listen gearbeitet werden kann, ist zumindest ein gutes Verständnis der Sprachen Lazy-Behavoir und des kleinen Details erforderlich, dass nicht alle Funktionen die Bewertung des zweiten Arguments erzwingen. Die Online-Grafiken für diese Funktionen sind für jemanden, der sie im College noch nie gesehen hat, verdammt verwirrend. Es gibt keinperldocÄquivalent. Ich kann keine einzige Beschreibung finden, was eine der Funktionen im Haskell-Vorspiel bewirkt. Das Vorspiel ist eine Art vorinstallierte Distribution, die mit dem Kern geliefert wird. Meine beste Ressource ist wirklich ein Typ, den ich noch nie getroffen habe (Cale), der mir mit enormen Kosten für seine eigene Zeit hilft.

Oh, und Fold muss die Liste nicht auf einen Skalar ohne Listentyp reduzieren. Die Identitätsfunktion für Listen kann geschrieben werden foldr (:) [] [1,2,3,4](Hervorhebungen, die Sie in einer Liste sammeln können).

Ich gehe zurück zum Lesen.

Evan Carroll
quelle
2
Diese Probleme wurden durch nicht strenge Bewertung verursacht, sodass sie Haskell-spezifisch sind.
JD
2

Hier sind einige Probleme, auf die ich gestoßen bin:

  1. Die meisten Menschen finden es schwierig, funktionale Programmierung zu verstehen. Dies bedeutet, dass es für Sie wahrscheinlich schwieriger sein wird, Funktionscode zu schreiben, und für andere wird es mit ziemlicher Sicherheit schwieriger sein, ihn zu erfassen.
  2. Funktionale Programmiersprachen sind normalerweise langsamer als eine Sprache wie c. Dies wird mit der Zeit immer weniger ein Problem (weil Computer schneller und Compiler intelligenter werden).
  3. Da sie nicht so weit verbreitet sind wie ihre zwingenden Gegenstücke, kann es schwierig sein, Bibliotheken und Beispiele für häufig auftretende Programmierprobleme zu finden. (Zum Beispiel ist es fast immer einfacher, etwas für Python zu finden, als für Haskell)
  4. Insbesondere zum Debuggen fehlen Tools. Es ist definitiv nicht so einfach wie das Öffnen von Visual Studio für C # oder Eclipse für Java.
Caleb
quelle
6
Haben Sie Zahlen oder Referenzen zur Unterstützung von Nummer 2? Auch für Nummer 4 wird F # eine erstklassige, vollständig unterstützte Sprache in Visual Studio 2010 sein
Russ Cam
8
Ich denke, die Aufzählungszeichen 2 bis 4 gehören nicht zur funktionalen Programmierung, sondern zu mehr Artefakten der Geschichte / Kultur / etc. (Das heißt, obwohl sie wahr sein mögen, sind sie 'wegen FP' nicht wahr, denke ich.)
Brian
4
Zu 1: Ich glaube nicht, dass das stimmt. Excel ist eine funktionale Programmiersprache, und ich habe nicht beobachtet, dass es schwieriger zu verstehen ist als beispielsweise C, BASIC, Pascal oder Python. In der Tat ist es wahrscheinlich umgekehrt.
Jörg W Mittag
6
Zu 2: Sprachen können nicht langsamer (oder schneller) sein als eine andere Sprache. Sprachen sind nur abstrakte Regeln, Sie können sie nicht ausführen. Nur Implementierungen können langsamer oder schneller sein als andere Implementierungen, aber dann sprechen Sie nicht mehr über Sprachen. Um das gleiche Problem zu lösen, müssen Sie am Ende die gleichen Schritte ausführen, daher wird die Leistung gleich sein. Der Supero Haskell-Compiler erzeugt beispielsweise Code, der 10% schneller ausgeführt wird als von GCC kompilierter handoptimierter C-Code. Gut implementierte Scheme-Compiler erzeugen Code, der zwischen halb so schnell und doppelt so schnell wie GCC ist.
Jörg W Mittag
3
Zu 4: Ich bin mir ziemlich sicher, dass jeder, der in den 90er Jahren jemals die Lisp Machine IDE verwendet hat, erstaunt sein wird, wie beschissen Eclipse und Visual Studio auch nach fast 20 Jahren noch sind. Das hat sowieso nichts mit funktionaler Programmierung zu tun. Wie gut Visual Studio ist, ist eine Funktion von Visual Studio und keine zwingende Programmierung. Tatsächlich bietet das F # Visual Studio-Plugin genau dieselben Funktionen wie die C # - und VB.NET-Plugins. Und wo Funktionalität fehlt, hat dies nichts mit funktionaler Programmierung zu tun und alles was mit dem Geldbetrag zu tun hat, den Microsoft für F # v C # bereitgestellt hat.
Jörg W Mittag
0

Wenn ich von den Details spezifischer Implementierungen der funktionalen Programmierung wegschaue, sehe ich zwei Hauptprobleme:

  1. Es scheint vergleichsweise selten, dass es praktisch ist, ein Funktionsmodell eines realen Problems einem imperativen vorzuziehen. Wenn die Problemdomäne unbedingt erforderlich ist, ist die Verwendung einer Sprache mit diesem Merkmal eine natürliche und vernünftige Wahl (da es im Allgemeinen ratsam ist, den Abstand zwischen Spezifikation und Implementierung zu minimieren, um die Anzahl subtiler Fehler zu verringern). Ja, dies kann durch einen ausreichend intelligenten Codierer überwunden werden. Wenn Sie jedoch Rockstar-Codierer für diese Aufgabe benötigen, liegt dies daran, dass es zu blutig ist.

  2. Aus irgendeinem Grund, den ich nie wirklich verstanden habe, möchten funktionale Programmiersprachen (oder vielleicht ihre Implementierungen oder Communitys?) Viel eher alles in ihrer Sprache haben. Bibliotheken, die in anderen Sprachen geschrieben sind, werden viel weniger verwendet. Wenn jemand anderes eine besonders gute Implementierung einer komplexen Operation hat, ist es viel sinnvoller, diese zu verwenden, anstatt eine eigene zu erstellen. Ich vermute, dass dies teilweise auf die Verwendung komplexer Laufzeiten zurückzuführen ist, die den Umgang mit Fremdcode (und insbesondere die effiziente Ausführung) ziemlich schwierig machen. Ich würde gerne in diesem Punkt als falsch erwiesen werden.

Ich nehme an, dass beide auf einen allgemeinen Mangel an Pragmatismus zurückzuführen sind, der dadurch verursacht wird, dass funktionale Programmierung von Programmierforschern viel stärker verwendet wird als gewöhnliche Codierer. Ein gutes Werkzeug kann es einem Experten ermöglichen, großartige Dinge zu tun, aber ein großartiges Werkzeug ermöglicht es dem einfachen Mann, sich dem zu nähern, was ein Experte normalerweise tun kann, da dies bei weitem die schwierigere Aufgabe ist.

Donal Fellows
quelle
2
Und es sollte auch beachtet werden, dass viele Sprachen nicht rein imperativ oder rein funktional sind, egal wie sie konventionell unterrichtet werden. Es gibt eine beträchtliche gegenseitige Befruchtung.
Donal Fellows