Erhöht die funktionale Programmierung die "Repräsentationslücke" zwischen Problemen und Lösungen? [geschlossen]

18

Da sich die Maschinensprache (z. B. 0110101000110101) in der Regel zu höheren Abstraktionsformen entwickelt hat, ist es im Allgemeinen einfacher, den Code zu verstehen, wenn er auf ein Problem angewendet wird. Assembler war eine Abstraktion über Maschinencode, C war eine Abstraktion über Assembler usw.

Objektorientiertes Design scheint sehr gut darin zu sein, ein Problem in Bezug auf Objekte zu modellieren, z. B. kann das Problem eines Universitätslehrgangs mit einer CourseKlasse, einer StudentKlasse usw. modelliert werden . Dann, wenn wir die Lösung schreiben In einer OO-Sprache haben wir ähnliche Klassen, die Verantwortlichkeiten haben und die im Allgemeinen für das Design hilfreich sind, insbesondere für die Modularisierung des Codes. Wenn ich dieses Problem 10 unabhängigen Teams gebe, die es mit einer OO-Methode lösen, haben die 10 Lösungen im Allgemeinen die Klassen gemeinsam, die sich auf das Problem beziehen. Es kann viele Unterschiede geben, wenn Sie anfangen, sich mit der Kopplung und Interaktion dieser Klassen zu befassen, also gibt es keine "Null-Repräsentationslücke".

Meine Erfahrung mit funktionaler Programmierung ist sehr begrenzt (keine reale Verwendung, nur Hello World-Programme). Ich verstehe nicht, wie solche Sprachen es ermöglichen, FP-Lösungen auf einfache Weise auf Probleme (mit einer geringen Repräsentationslücke) abzubilden, wie dies bei OO-Sprachen der Fall ist.

Ich verstehe die Vorteile von FP in Bezug auf die gleichzeitige Programmierung. Aber fehlt mir etwas oder geht es in FP nicht darum, eine Repräsentationslücke zu schließen (um das Verständnis von Lösungen zu erleichtern)?

Eine andere Möglichkeit, dies zu fragen: Würde der FP-Code von 10 verschiedenen Teams, die dasselbe Problem in der realen Welt lösen, viel gemeinsam haben?


Aus Wikipedia über Abstraktion (Informatik) (Schwerpunkt Mine):

Funktionale Programmiersprachen weisen üblicherweise Abstraktionen auf, die sich auf Funktionen beziehen , wie Lambda-Abstraktionen (Verwandeln eines Begriffs in eine Funktion einer Variablen), Funktionen höherer Ordnung (Parameter sind Funktionen), Klammer-Abstraktion (Verwandeln eines Begriffs in eine Funktion einer Variablen).

Die Repräsentationslücke könnte potenziell vergrößert werden, da [einige] Probleme der realen Welt mit solchen Abstraktionen nicht einfach modelliert werden können.


Eine andere Möglichkeit, die Abnahme der Repräsentationslücke zu erkennen, besteht darin, die Lösungselemente auf das Problem zurückzuführen. Das 0s und 1s im Maschinencode ist sehr schwer zurückzuverfolgen, wohingegen die StudentKlasse leicht zurückzuverfolgen ist. Nicht alle OO-Klassen lassen sich leicht auf den Problembereich zurückführen, aber viele tun dies.

Müssen FP-Abstraktionen nicht immer erklärt werden, um herauszufinden, welchen Teil des Problemraums sie lösen (abgesehen von mathematischen Problemen)?OK - ich bin gut in diesem Teil. Wenn ich mir viele weitere Beispiele anschaue, sehe ich, wie deutlich die FP-Abstraktionen für Teile des Problems sind, die in der Datenverarbeitung zum Ausdruck kommen.


Die akzeptierte Antwort auf eine verwandte Frage Kann UML zur Modellierung eines Funktionsprogramms verwendet werden? - sagt "Funktionale Programmierer haben nicht viel mit Diagrammen zu tun." Es ist mir wirklich egal, ob es sich um UML handelt, aber ich frage mich, ob FP-Abstraktionen einfach zu verstehen / zu kommunizieren sind, wenn es keine weit verbreiteten Diagramme gibt (vorausgesetzt, diese Antwort ist richtig). Auch hier ist mein Kenntnisstand in Bezug auf die Verwendung / das Verständnis von FP trivial, sodass ich keine Diagramme für einfache FP-Programme benötige.

OO-Design verfügt über Abstraktionsebenen für Funktionen / Klassen / Pakete mit jeweils einer Kapselung (Zugriffskontrolle, Ausblenden von Informationen), wodurch die Verwaltung der Komplexität vereinfacht wird. Dies sind Elemente, die den Übergang vom Problem zur Lösung und zurück erleichtern.


Viele Antworten sprechen davon, wie Analyse und Design in FP auf eine Art und Weise analog zu OO durchgeführt werden, aber bis jetzt hat noch niemand etwas Hochrangiges angeführt (Paul hat einige interessante Dinge angeführt, aber es ist niedrigrangig). Ich habe gestern viel gegoogelt und interessante Diskussionen gefunden. Das Folgende ist aus Refactoring Functional Programs von Simon Thompson (2004) (Schwerpunkt Mine)

Beim Entwurf eines objektorientierten Systems wird vorausgesetzt, dass der Entwurf der Programmierung vorausgeht. Entwürfe werden mit einem System wie UML geschrieben, das in Tools wie Eclipse unterstützt wird. Anfänger können einen visuellen Entwurfsansatz mit Systemen wie BlueJ erlernen. Über die Arbeit an einer ähnlichen Methodik für die funktionale Programmierung wird in FAD: Functional Analysis and Design berichtet , es gibt jedoch nur wenige andere Arbeiten. Dafür kann es eine Reihe von Gründen geben.

  • Bestehende Funktionsprogramme sind von einer Größe, die kein Design erfordert. Viele Funktionsprogramme sind klein, aber andere, wie der Glasgow Haskell Compiler, sind umfangreich.

  • Funktionsprogramme modellieren direkt die Anwendungsdomäne und machen so das Design irrelevant. Während funktionale Sprachen eine Vielzahl leistungsfähiger Abstraktionen bieten, ist es schwierig zu behaupten, dass diese alle und nur die zur Modellierung der realen Welt erforderlichen Abstraktionen bereitstellen.

  • Funktionsprogramme werden als eine sich entwickelnde Reihe von Prototypen erstellt.

In der oben zitierten Dissertation werden die Vorteile des Einsatzes von Analyse- und Entwurfsmethoden (ADM) unabhängig von Paradigmen skizziert. Es wird jedoch argumentiert, dass ADMs mit dem Implementierungsparadigma in Einklang gebracht werden sollten. Das heißt, OOADM eignet sich am besten für die OO-Programmierung und lässt sich nicht gut auf ein anderes Paradigma wie FP anwenden. Hier ist ein großartiges Zitat, das meiner Meinung nach die so genannte Repräsentationslücke umschreibt:

Man kann sich ausführlich darüber streiten, welches Paradigma die beste Unterstützung für die Softwareentwicklung bietet, aber man erreicht das natürlichste, effizienteste und effektivste Entwicklungspaket, wenn man innerhalb eines einzigen Paradigmas von der Problembeschreibung bis zur Implementierung und Bereitstellung bleibt.

Hier sind die von FAD vorgeschlagenen Diagramme:

  • Funktionsabhängigkeitsdiagramme, die eine Funktion mit denen darstellen, die sie in ihrer Implementierung verwendet;
  • Typabhängigkeitsdiagramm, das den gleichen Dienst für Typen bereitstellt; und,
  • Modulabhängigkeitsdiagramme, die Ansichten der Modularchitektur des Systems darstellen.

In Abschnitt 5.1 der FAD-Arbeit gibt es eine Fallstudie, die ein System zur Automatisierung der Datenerfassung für eine Fußballliga darstellt. Die Anforderungen sind zu 100% funktional, z. B. Eingabe von Fußballergebnissen, Erstellung von Ranglisten, Wertungslisten, Anwesenheitstabellen, Übertragung von Spielern zwischen Mannschaften, Aktualisierung von Daten nach neuen Ergebnissen usw. Es wird nicht erwähnt, wie FAD bei der Lösung nichtfunktionaler Anforderungen vorgeht Abgesehen von der Feststellung, dass "neue Funktionalität zu minimalen Kosten erlaubt sein sollte", was praktisch unmöglich zu testen ist.

Abgesehen von FAD sehe ich leider keine modernen Referenzen für Modellierungssprachen (visuell), die für FP vorgeschlagen werden. UML ist ein anderes Paradigma, deshalb sollten wir das vergessen.

Fuhrmanator
quelle
1
Kommentare sind nicht für eine längere Diskussion gedacht. Diese Unterhaltung wurde in den Chat verschoben .
maple_shaft

Antworten:

20

Die Grunddaten sind in so ziemlich jedem Paradigma gleich aufgebaut. Du wirst eine habenStudent , ein Courseusw. haben, egal ob es ein Objekt, eine Struktur, eine Aufzeichnung oder was auch immer ist. Der Unterschied zu OOP besteht nicht darin, wie die Daten strukturiert sind, sondern darin, wie die Funktionen strukturiert sind.

Tatsächlich finde ich funktionale Programme, die meiner Meinung nach viel besser zu einem Problem passen. Um beispielsweise den Stundenplan eines Studenten für das nächste Semester zu planen, denken Sie an Dinge wie Listen von Kursen, die ein Student abgeschlossen hat, Kurse in einem Studiengang, Kurse, die in diesem Semester angeboten werden, Kurse, für die ein Student die Voraussetzungen erfüllt hat, Kurse mit Zeiten, die nicht gültig sind nicht widersprechen, etc.

Plötzlich ist nicht mehr klar, welche Klasse all diese Listen erstellen und speichern soll. Noch weniger, wenn Sie komplexe Kombinationen dieser Listen haben. Sie müssen jedoch eine Klasse auswählen.

In FP schreiben Sie Funktionen, die einen Studenten und eine Liste von Kursen aufnehmen und eine gefilterte Liste von Kursen zurückgeben. Sie können alle diese Funktionen in einem Modul zusammenfassen. Sie müssen es nicht mit der einen oder anderen Klasse koppeln.

Daher sehen Ihre Datenmodelle im Endeffekt eher so aus, wie OOP-Programmierer ihre Modelle sehen, bevor sie mit Klassen verschmutzt werden, die keinen anderen Zweck haben, als praktische Stellen zum Platzieren von Funktionen bereitzustellen, die mit Kombinationen anderer Klassen arbeiten. Keine seltsamen CourseStudentFilterListoder ähnlichen Klassen, die Sie am Ende immer in OOP benötigen, aber denken Sie nie an den Anfang des Designs.

Karl Bielefeldt
quelle
5
@BenAaronson das Problem ist meiner Erfahrung nach, dass dann jede Funktion, die sich auf einen Schüler bezieht, zur Schülerklasse hinzugefügt wird und ihre Verantwortlichkeiten wachsen und wachsen, bis Sie sich vorstellen müssen StudentCourseFilterer, um die Dinge handhabbar zu halten
AlexFoxGill
3
@ Den Hybrid-Ansätze haben ihre Vorzüge. Es ist jedoch nicht wahr, dass die Unterscheidung künstlich ist. Es gibt viele Kompromisse, die Scala eingehen musste, um OOP und FP anzupassen (weniger aussagekräftige Typinferenz ist eine. Komplexität ist eine andere: Ja, eine Sprache, die OOP und FP mischt, ist tendenziell komplexer - wie in "schwer zu verwenden" und verstehe "- als einer seiner reineren Brüder auf den beiden Extremen).
Andres F.
3
@Den Siehe auch Erik Meijers "'Mostly Functional'-Programmierung funktioniert nicht" , was gegen den hybriden Ansatz und für eine vollständige "fundamentalistische" FP spricht.
Andres F.
4
@Den Die Popularität, die Sie beschreiben, ist leider ein Unfall der Geschichte. Ich benutze Scala bei der Arbeit und es ist ein komplexes Biest, da es versucht, FP und OOP zu mischen. Der Wechsel zu einer echten FP-Sprache wie Haskell fühlt sich wie ein Hauch frischer Luft an - und ich spreche auch nicht aus akademischer Perspektive!
Andres F.
3
@Den Ich kann nicht sagen, was eine dieser Funktionen macht. Ich kann Ihnen jedoch sagen, ob die erste Funktion in FP eine Sortierung oder ein Filter war, sie würde filter oder sort heißen , nicht func(so wie Sie es genannt IFilterhaben usw.). Im Allgemeinen würden Sie es in FP benennen funcoder fwenn entweder sortoder filtereine gültige Wahl ist! Zum Beispiel beim Schreiben einer Funktion höherer Ordnung, die funcals Parameter verwendet wird.
Andres F.
10

Als ich vor Jahren meinen Java-Kurs belegte, wurde von uns erwartet, dass wir der gesamten Klasse unsere Lösungen zeigen, damit ich sehen konnte, wie die Leute denken. wie sie Probleme logisch lösen. Ich habe voll und ganz damit gerechnet, dass sich die Lösungen um drei oder vier gemeinsame Lösungen gruppieren. Stattdessen beobachtete ich, wie 30 Schüler das Problem auf 30 völlig unterschiedliche Arten lösten.

Wenn junge Programmierer Erfahrung sammeln, lernen sie natürlich gängige Softwaremuster kennen, verwenden diese Muster in ihrem Code und ihre Lösungen vereinen sich möglicherweise mit einigen optimalen Strategien. Diese Muster bilden eine technische Sprache, mit der erfahrene Entwickler kommunizieren können.

Die technische Sprache, die der funktionalen Programmierung zugrunde liegt, ist Mathematik . Dementsprechend sind die Probleme, die zur Lösung mit funktionaler Programmierung am besten geeignet sind, im Wesentlichen mathematische Probleme. Mit Mathe meine ich nicht die Art von Mathe, die Sie in Geschäftslösungen sehen würden, wie Addition und Subtraktion. Ich spreche eher von der Art von Mathematik, die Sie in Math Overflow sehen könnten, oder von der Art von Mathematik, die Sie in der Orbitz-Suchmaschine sehen würden (in Lisp geschrieben).

Diese Orientierung hat einige wichtige Konsequenzen für funktionierende Programmierer, die versuchen, reale Programmierprobleme zu lösen:

  1. Funktionale Programmierung ist eher deklarativ als imperativ. Es geht in erster Linie darum , dem Computer zu sagen, was er tun soll und nicht, wie er es tun soll.

  2. Objektorientierte Programme werden häufig von oben nach unten erstellt. Es wird ein Klassendesign erstellt und die Details werden ausgefüllt. Funktionsprogramme werden häufig von Grund auf erstellt, beginnend mit kleinen, detaillierten Funktionen, die zu übergeordneten Funktionen kombiniert werden.

  3. Funktionale Programmierung kann Vorteile für die Prototyperstellung komplexer Logik, die Erstellung flexibler Programme, die sich ändern und organisch weiterentwickeln können, und die Erstellung von Software haben, bei der das ursprüngliche Design unklar ist.

  4. Objektorientierte Programme eignen sich besser für Geschäftsdomänen, da die Klassen, Nachrichten zwischen Objekten und die Softwaremuster eine Struktur bereitstellen, die der Geschäftsdomäne zugeordnet ist, deren Business Intelligence erfasst und dokumentiert.

  5. Da jede praktische Software Nebenwirkungen (I / O) erzeugt, benötigen rein funktionale Programmiersprachen einen Mechanismus, um diese Nebenwirkungen zu erzeugen, während sie mathematisch rein bleiben (Monaden).

  6. Funktionale Programme können aufgrund ihrer mathematischen Natur leichter bewiesen werden. Das Typensystem von Haskell findet beim Kompilieren Dinge, die die meisten OO-Sprachen nicht können.

Und so weiter. Wie bei vielen Dingen im Computer haben objektorientierte Sprachen und funktionale Sprachen unterschiedliche Kompromisse.

Einige moderne OO-Sprachen haben einige der nützlichen funktionalen Programmierkonzepte übernommen, sodass Sie das Beste aus beiden Welten genießen können. Linq und die Sprachfunktionen, die hinzugefügt wurden, um es zu unterstützen, sind ein gutes Beispiel dafür.

Robert Harvey
quelle
6
@thepacker: Du hast Stand in der Funktionsprogrammierung. Nur die Darstellung ist anders: In OOP verwenden Sie veränderbare Variablen, während Sie in der funktionalen Programmierung unendliche Zustandsströme verwenden können, die im laufenden Betrieb erstellt werden, wenn das Programm sie überprüfen muss. Leider wird der Zustand oft mit veränderlichen Variablen identifiziert, als ob veränderliche Variablen die einzige Möglichkeit wären, den Zustand darzustellen. Infolgedessen glauben viele, dass eine Programmiersprache ohne veränderbare Variablen den Zustand nicht modellieren kann.
Giorgio
12
"Dementsprechend sind die Probleme, die sich am besten mit funktionaler Programmierung lösen lassen, im Wesentlichen mathematische Probleme.": Ich bin mit dieser Aussage nicht einverstanden (siehe auch meinen vorherigen Kommentar), zumindest verstehe ich nicht, warum es wahr sein sollte oder was es ist bedeutet eigentlich. Sie können das Durchlaufen einer Verzeichnisstruktur als mathematisches Problem ansehen und es mit einer rekursiven Funktion implementieren. Da der Kunde den Quellcode jedoch nicht sieht, lösen Sie nur ein praktisches Problem wie das Suchen einer Datei irgendwo im Dateisystem. Ich finde eine solche Unterscheidung zwischen mathematischen und nicht-mathematischen Problemen künstlich.
Giorgio
5
+1, aber ich bin nicht begeistert von den Punkten 2 und 4. Ich habe viele Haskell-Tutorials und Blog-Posts gesehen, die damit beginnen, ein Problem von oben nach unten anzugehen und alle Typen und Vorgänge zu definieren, um zu sehen, wie alles zusammenpasst Lassen Sie jeden Wert und jede Funktionsdefinition undefiniert (also definiert, um eine Ausnahme auszulösen). Es scheint mir , dass Haskell Programmierer neigen dazu , das Problem zu modellieren gründlichen , weil sie mehr geneigt sind aus Gründen der Semantik trivial Typen hinzuzufügen - zB eine Zugabe von SafeStringTyp - Strings darzustellen , die HTML-entgangen waren - und in der Regel mehr Spur zu halten Auswirkungen.
Doval
4
"Die Probleme, die sich am besten mit funktionaler Programmierung lösen lassen, sind im Wesentlichen mathematische Probleme." Das stimmt einfach nicht. Die Tatsache, dass das Konzept der Monaden aus der Kategorietheorie stammt, bedeutet nicht, dass mathematische Aufgaben die natürlichste / passendste Domäne für funktionale Sprachen sind. Es bedeutet lediglich, dass in stark typisierten funktionalen Sprachen geschriebener Code mithilfe von Mathematik beschrieben, analysiert und begründet werden kann. Dies ist eine leistungsstarke Zusatzfunktion, die Sie nicht davon abhält, "Barbie Horse Adventures" in Haskell zu schreiben.
Itsbruce
6
@itsbruce Barbie Horse Adventures ist eine ernstzunehmende mathematische Simulation des höchsten Kalibers. Es handelt sich um Pidgeon Holing FP in übermäßig komplexen numerischen Projektionen wie Matrizen, die das physikalische Schimmern von Barbies Haar über einen diskreten Zeitraum in einer Vielzahl von möglichen realen Umgebungen beschreiben, die die Menschen überzeugen Verwerfen Sie es vollständig als irrelevant für die alltägliche Kodierung von Geschäftsproblemen.
Jimmy Hoffa
7

Ich möchte einen Aspekt hervorheben, den ich für wichtig halte und der in den anderen Antworten nicht behandelt wurde.

Zuallererst denke ich, dass die repräsentative Kluft zwischen Problemen und Lösungen je nach Hintergrund und den ihnen vertrauten Konzepten für den Programmierer größer sein kann.

OOP und FP betrachten Daten und Operationen aus zwei unterschiedlichen Perspektiven und bieten unterschiedliche Kompromisse, wie Robert Harvey bereits ausgeführt hat.

Ein wichtiger Aspekt, in dem sie sich unterscheiden, ist die Art und Weise, in der sie es ermöglichen, Ihre Software zu erweitern.

Stellen Sie sich die Situation vor, in der Sie über eine Sammlung von Datentypen und Operationen verfügen, z. B. unterschiedliche Bildformate und eine Bibliothek von Algorithmen zur Verarbeitung von Bildern.

Die funktionale Programmierung erleichtert das Hinzufügen neuer Operationen zu Ihrer Software: Sie müssen lediglich eine lokale Änderung in Ihrem Code vornehmen, dh Sie fügen eine neue Funktion hinzu, die verschiedene Eingabedatenformate verarbeitet. Andererseits ist das Hinzufügen neuer Formate aufwändiger: Sie müssen alle bereits implementierten Funktionen ändern (nicht lokale Änderung).

Der objektorientierte Ansatz ist dazu symmetrisch: Jeder Datentyp trägt eine eigene Implementierung aller Operationen und ist dafür verantwortlich, zur Laufzeit die richtige Implementierung auszuwählen (dynamisches Dispatching). Dies erleichtert das Hinzufügen eines neuen Datentyps (z. B. eines neuen Bildformats): Sie fügen einfach eine neue Klasse hinzu und implementieren alle Methoden. Andererseits bedeutet das Hinzufügen einer neuen Operation, dass alle Klassen geändert werden, die diese Operation bereitstellen müssen. In vielen Sprachen geschieht dies, indem eine Schnittstelle erweitert und alle Klassen, die sie implementieren, angepasst werden.

Dieser unterschiedliche Ansatz zur Erweiterung ist einer der Gründe, warum OOP für bestimmte Probleme besser geeignet ist, bei denen die Menge der Vorgänge weniger häufig variiert als die Menge der Datentypen, für die diese Vorgänge ausgeführt werden. Ein typisches Beispiel sind GUIs: Sie einen festen Satz von Operationen haben , dass alle Widgets implementieren müssen ( paint, resize, move, usw.) und eine Sammlung von Widgets , die Sie erweitern möchten.

Entsprechend dieser Dimension sind OOP und FP nur zwei Möglichkeiten, Ihren Code zu organisieren. Siehe SICP , insbesondere Abschnitt 2.4.3, Tabelle 2.22 und Absatz Message Passing .

Zusammenfassen: In OOP verwenden Sie Daten zum Organisieren von Vorgängen, in FP verwenden Sie Vorgänge zum Organisieren von Daten. Jeder Ansatz ist je nach Kontext stärker oder schwächer. Im Allgemeinen weist keine der beiden eine größere Repräsentationslücke zwischen Problem und Lösung auf.

Giorgio
quelle
1
Tatsächlich gibt Ihnen das Besuchermuster (in OOP) die gleiche Leistung, die Sie für FP angegeben haben. Sie können neue Operationen hinzufügen, während das Hinzufügen neuer Klassen erschwert wird. Ich frage mich, ob Sie dasselbe in FP tun können (z. B. Hinzufügen neuer Formate anstelle von Operationen).
Euphorischer
1
Das Bildverarbeitungsszenario scheint nicht das beste Beispiel zu sein. Sicherlich würden Sie die Bilder in ein internes Format konvertieren und dieses verarbeiten, anstatt separate Implementierungen aller Ihrer Bildverarbeitungssachen für jeden Bildtyp zu schreiben?
Hey
1
@ Giorgio Vielleicht verstehe ich die beabsichtigte Bedeutung dieses Teils nicht: "Das Hinzufügen neuer Formate ist komplizierter: Sie müssen alle Funktionen ändern, die Sie bereits implementiert haben."
Hey
2
@ Hey Giorgio bezieht sich wahrscheinlich auf das sogenannte Ausdrucksproblem . "Hinzufügen neuer Formate" bedeutet "Hinzufügen neuer Konstruktoren (auch" Fälle "genannt) zu einem Datentyp".
Andres F.
1
@Euphoric: Ich habe nicht auf die Details eingegangen, aber das FP-Gegenstück zum Besuchermuster sollte eine OtherVariante / einen Konstruktor in Ihrem Datentyp und einen zusätzlichen Funktionsparameter enthalten, der an alle Funktionen übergeben wird, um den anderen Fall zu behandeln. In Bezug auf die OOP-Lösung (dynamischer Versand) ist dies natürlich umständlich / weniger natürlich. Ebenso ist das Besuchermuster umständlich / weniger natürlich als die FP-Lösung (eine Funktion höherer Ordnung).
Giorgio
5

Die meisten funktionalen Sprachen sind nicht objektorientiert. Das heißt nicht, dass sie keine Objekte haben (im Sinne komplexer Typen, denen eine bestimmte Funktionalität zugeordnet ist). Haskell hat wie Java Listen, Maps, Arrays, alle Arten von Bäumen und viele andere komplexe Typen. Wenn Sie sich das Haskell List- oder Map-Modul ansehen, sehen Sie eine Reihe von Funktionen, die den Methoden in den meisten äquivalenten OO-Sprachbibliotheksmodulen sehr ähnlich sind. Wenn Sie den Code überprüfen, werden Sie sogar eine ähnliche Kapselung mit einigen Typen (oder deren Konstruktoren, um genau zu sein) und Funktionen finden, die nur von anderen Funktionen im Modul verwendet werden können.

Die Art und Weise, wie Sie mit diesen Typen in Haskell arbeiten, unterscheidet sich häufig kaum von der OO-Art. In Haskell sage ich

  null xs
  length xs

und in Java sagst du

  xs.isEmpty
  xs.length

Tomate, Tomate. Die Haskell-Funktionen sind nicht an das Objekt gebunden, sondern eng mit dessen Typ verknüpft. "Ah, aber" , sagen Sie, "in Java ruft es auf, welche Methode mit welcher Länge für die tatsächliche Klasse dieses Objekts geeignet ist". Überraschung - Haskell macht Polymorphismus auch mit Typklassen (und anderen Dingen).

In Haskell, wenn ich ist - eine Sammlung von Zahlen - spielt es keine Rolle , ob die Auflistung eine Liste oder Satz oder ein Array ist, dieser Code

  fmap (* 2) is

Gibt eine Sammlung mit allen Elementen zurück, die verdoppelt wurden. Polymorphismus bedeutet, dass die entsprechende Kartenfunktion für den bestimmten Typ aufgerufen wird.

Diese Beispiele sind in Wahrheit keine wirklich komplexen Typen, aber das Gleiche gilt für schwierigere Probleme. Die Idee, dass Sie mit funktionalen Sprachen keine komplexen Objekte modellieren und ihnen bestimmte Funktionen zuordnen können, ist einfach falsch.

Es gibt wichtige und signifikante Unterschiede zwischen dem funktionalen und dem OO-Stil, aber ich denke nicht, dass sich diese Antwort damit befassen muss. Sie haben gefragt, ob funktionale Sprachen die intuitive Modellierung von Problemen und Aufgaben verhindern (oder behindern). Die Antwort ist nein.

itsbruce
quelle
Tatsächlich können Sie in Java / C # Typklassen verwenden. Sie müssen nur das Funktionswörterbuch explizit übergeben, anstatt den Compiler anhand des Typs den richtigen ableiten zu lassen.
Doval
Oh, auf jeden Fall. Wie William Cook sagte, verwendet viel OO-Code tatsächlich häufiger Funktionen höherer Ordnung als Funktionscode, obwohl der Codierer dies wahrscheinlich nicht weiß.
Itsbruce
2
Sie machen eine falsche Unterscheidung. Eine Liste ist nicht mehr eine inerte Datenstruktur als ein Stapel oder eine Warteschlange oder ein Tic-Tac-Toe-Simulator. Eine Liste kann alles enthalten (in Haskell kann es sich durchaus um teilweise angewendete Funktionen handeln) und definiert, wie mit diesen Dingen interagiert werden kann. Sie könnten eine Liste mit teilweise angewendeten Funktionen auf eine andere Werteliste anwenden, und das Ergebnis wäre eine neue Liste, die alle möglichen Kombinationen von Funktionen aus der ersten und Eingaben aus der zweiten enthält. Das ist viel mehr als eine C-Struktur.
Itsbruce
3
Typen werden für vielfältigere Dinge in funktionalen Sprachen verwendet als Imperative / OO-Typen (die Typensysteme sind zum einen in der Regel leistungsfähiger und konsistenter). Typen und ihre Funktionen werden zum Beispiel verwendet, um Codepfade zu formen (weshalb einige Funktionssprachen einfach keine Schlüsselwörter für Schleifen haben - nicht erforderlich).
itsbruce
4
@Fuhrmanator "Ich sehe nicht, wie das in FP gemacht wird" Sie beschweren sich also über etwas, das Sie nicht verstehen und das keinen Sinn ergibt. Geh und lerne es, verstehe es gründlich und dann wird es Sinn machen. Vielleicht, nachdem es Sinn macht, werden Sie entscheiden, dass es Mist ist und Sie nicht andere brauchen, um es Ihnen zu beweisen, obwohl andere Leute wie oben es verstehen und zu dieser Schlussfolgerung nicht gekommen sind. Sie scheinen ein Messer zu einem Schusswechsel mitgebracht zu haben, und jetzt sagen Sie allen, dass Waffen nutzlos sind, weil sie nicht scharf sind.
Jimmy Hoffa
4

FP strebt in der Tat eine Verringerung der Repräsentationslücke an:

In funktionalen Sprachen werden Sie häufig feststellen, dass es üblich ist, die Sprache (mithilfe von Bottom-up-Design) in eine eingebettete domänenspezifische Sprache (EDSL) aufzubauen . Auf diese Weise können Sie ein Mittel entwickeln, um Ihre geschäftlichen Bedenken auf eine Weise auszudrücken, die für Ihre Domain in der Programmiersprache selbstverständlich ist. Haskell und Lisp sind beide stolz darauf.

Ein Teil (wenn nicht alles) dessen, was diese Fähigkeit ermöglicht, ist mehr Flexibilität und Ausdruckskraft innerhalb der Basissprache (n) selbst; Mit erstklassigen Funktionen, Funktionen höherer Ordnung, Funktionszusammenstellung und in einigen Sprachen algebraischen Datentypen (AKA-diskriminierte Gewerkschaften) und der Möglichkeit, Operatoren * zu definieren, steht mehr Flexibilität für das Ausdrücken von Dingen zur Verfügung als mit OOP Das bedeutet, dass Sie wahrscheinlich auf natürlichere Weise Dinge aus Ihrer Problemdomäne zum Ausdruck bringen können. (Es sollte etwas heißen, dass viele OOP-Sprachen in letzter Zeit viele dieser Funktionen übernommen haben, wenn sie nicht gerade damit anfangen.)

* Ja, in vielen OOP-Sprachen können Sie die Standardoperatoren überschreiben, aber in Haskell können Sie neue definieren!

Aufgrund meiner Erfahrung mit funktionalen Sprachen (hauptsächlich F # und Haskell, etwas Clojure und ein wenig Lisp und Erlang) kann ich den Problembereich leichter in die Sprache abbilden als mit OOP - insbesondere mit algebraischen Datentypen. Das finde ich flexibler als der Unterricht. Als ich mit FP angefangen habe, warf mich vor allem mit Haskell die Frage auf, ob ich auf einer etwas höheren Ebene denken / arbeiten musste, als ich es in Imperativ- oder OOP-Sprachen gewohnt war. Sie könnten auch ein bisschen darauf stoßen.

paul
quelle
Geschäftliche Bedenken (des Kunden) werden in übergeordneten Funktionen nicht sehr oft geäußert. Ein Kunde kann jedoch eine einzeilige User Story schreiben und Ihnen Geschäftsregeln bereitstellen (oder Sie können diese aus dem Kunden herausholen). Ich interessiere mich für die (von oben nach unten?) Domänenmodellierung (aus diesen Artefakten), die es uns ermöglicht, zu Abstraktionen des Problems zu gelangen, die erstklassigen Funktionen usw. zugeordnet sind. Können Sie einige Referenzen anführen? Können Sie ein konkretes Beispiel für eine Domain, die Abstraktionen usw. verwenden?
Fuhrmanator
3
@Fuhrmanator Ein Kunde kann weiterhin einzeilige User Stories und Geschäftsregeln schreiben, unabhängig davon, ob Sie OOP und FP verwenden . Ich erwarte nicht, dass Kunden Funktionen höherer Ordnung so gut verstehen wie Vererbung, Zusammensetzung oder Schnittstellen.
Andres F.
1
@Fuhrmanator Wie oft hat der Kunde Bedenken in Bezug auf Java-Generika oder abstrakte Basisklassen und Schnittstellen geäußert? Lieber Gott. Paul hat Ihnen eine sehr gute Antwort gegeben und eine praktische und zuverlässige Methode zur Modellierung mit Techniken erklärt, die sicherlich nicht zu schwer zu visualisieren sind. Und doch bestehen Sie darauf, dass es in Bezug auf eine bestimmte Modellierungstechnik für ein bestimmtes Paradigma beschrieben wird, als ob dies irgendwie die natürlichste Art wäre, Design darzustellen, und alles andere kann in seinen Begriffen neu gezeichnet werden.
itsbruce
@Fuhrmanator Dies ist möglicherweise nicht so anspruchsvoll, wie Sie es suchen, sollte jedoch einen Einblick in den Entwurfsprozess mit funktionalen Techniken geben: Entwurf mit Typen . Ich würde empfehlen, diese Website im Allgemeinen zu durchsuchen, da sie einige hervorragende Artikel enthält.
Paul
@Fuhrmanator Es gibt auch diese Serie Thinking Functionally
Paul
3

Wie Niklaus Wirth es ausdrückte, "Algorithmen + Datenstrukturen = Programme". Bei der funktionalen Programmierung geht es um die Organisation von Algorithmen, und es wird nicht viel über die Organisation von Datenstrukturen gesagt. In der Tat gibt es FP-Sprachen mit veränderlichen (Lisp) und unveränderlichen (Haskell, Erlang) Variablen. Wenn Sie FP mit etwas vergleichen und kontrastieren möchten, sollten Sie imperative (C, Java) und deklarative (Prolog) Programmierung wählen.

OOP ist andererseits eine Möglichkeit, Datenstrukturen aufzubauen und Algorithmen an diese anzuhängen. FP hindert Sie nicht daran, OOP-ähnliche Datenstrukturen aufzubauen. Wenn Sie mir nicht glauben, schauen Sie sich die Haskell-Typdeklarationen an. Die meisten FP-Sprachen stimmen jedoch darin überein, dass Funktionen nicht zu Datenstrukturen gehören und sich im selben Namespace befinden sollten. Dies geschieht, um das Erstellen neuer Algorithmen über die Funktionszusammensetzung zu vereinfachen. Wenn Sie wissen, welche Eingabe eine Funktion benötigt und welche Ausgabe sie erzeugt, warum sollte es dann wichtig sein, zu welcher Datenstruktur sie gehört? Warum muss beispielsweise addfunction für eine Instanz vom Typ Integer aufgerufen und ein Argument übergeben werden, anstatt einfach zwei Integer-Argumente zu übergeben?

Ich verstehe daher nicht, warum FP das Verständnis von Lösungen generell erschweren sollte, und ich denke nicht, dass es das überhaupt tut. Denken Sie jedoch daran, dass ein erfahrener imperativer Programmierer funktionale Programme mit Sicherheit schwerer zu verstehen findet als imperative und umgekehrt. Dies ähnelt der Windows-Linux-Dichotomie, wenn Leute, die 10 Jahre in das Erlernen der Windows-Umgebung investiert haben, nach einem Monat Schwierigkeiten mit Linux haben und Linux-Gewohnte unter Windows nicht die gleiche Produktivität erzielen können. Daher ist die 'Repräsentationslücke', von der Sie sprechen, sehr subjektiv.

mkalkov
quelle
Aufgrund der Kapselung und des Versteckens von Daten, die per se keine OO-Prinzipien sind, bin ich nicht ganz damit einverstanden, dass OO Datenstrukturen + Algorithmen sind (das ist nur die interne Sichtweise). Das Schöne an jeder guten Abstraktion ist, dass es einen Service gibt, der einen Teil des Problems löst, ohne dass man zu viele Details verstehen muss. Ich denke, Sie sprechen von Kapselung, wenn Sie Funktionen und Daten in FP trennen, aber ich bin zu grün, um es genau zu wissen. Außerdem habe ich meine Frage bearbeitet, um auf die Rückverfolgung von der Lösung bis zum Problem zu verweisen (um die Bedeutung der Repräsentationslücke zu klären).
Fuhrmanator
if you know what input a function takes and what output it produces, why should it matter which data structure it belongs too?Das klingt nach Zusammenhalt, der für jedes modulare System wichtig ist. Ich würde argumentieren, dass die Unkenntnis, wo eine Funktion zu finden ist, das Verständnis einer Lösung erschweren würde, was auf eine größere Repräsentationslücke zurückzuführen ist. Viele OO-Systeme haben dieses Problem, aber es liegt an schlechtem Design (geringer Kohäsion). Auch hier ist das Namespace-Problem auf meine Erfahrung mit FP zurückzuführen. Vielleicht ist es nicht so schlimm, wie es sich anhört.
Fuhrmanator
1
@Fuhrmanator Aber Sie wissen , wo Sie eine Funktion finden. Und es gibt nur eine Funktion, nicht mehrere Funktionen mit demselben Namen, die für jeden Datentyp definiert sind (siehe auch "Ausdrucksproblem"). Für Haskell-Bibliotheksfunktionen (zu deren Verwendung Sie aufgefordert werden, anstatt das Rad neu zu entdecken oder etwas weniger nützliche Versionen dieser Funktionen zu erstellen ) stehen Ihnen beispielsweise hervorragende Suchwerkzeuge wie Hoogle zur Verfügung , mit denen Sie so leistungsstark suchen können durch Unterschrift (probieren Sie es aus! :))
Andres F.
1
@Fuhrmanator, nachdem "Das klingt sehr nach Zusammenhalt", hätte ich erwartet, dass Sie logischerweise zu dem Schluss kommen, dass Sie mit FP-Sprachen Programme mit höherem Zusammenhalt schreiben können, aber Sie haben mich überrascht. :-) Ich denke, du solltest eine FP-Sprache auswählen, sie lernen und für dich selbst entscheiden. Wenn ich Sie wäre, würde ich vorschlagen, mit Haskell zu beginnen, da es ziemlich rein ist und Sie daher keinen Code im imperativen Stil schreiben lassen. Erlang und einige Lisp sind auch eine gute Wahl, aber sie mischen sich in andere Programmierstile. Scala und Clojure, IMO, sind ziemlich kompliziert anzufangen.
Mkalkov