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 Course
Klasse, einer Student
Klasse 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 0
s und 1
s im Maschinencode ist sehr schwer zurückzuverfolgen, wohingegen die Student
Klasse 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.
Antworten:
Die Grunddaten sind in so ziemlich jedem Paradigma gleich aufgebaut. Du wirst eine haben
Student
, einCourse
usw. 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
CourseStudentFilterList
oder ähnlichen Klassen, die Sie am Ende immer in OOP benötigen, aber denken Sie nie an den Anfang des Designs.quelle
StudentCourseFilterer
, um die Dinge handhabbar zu haltenfunc
(so wie Sie es genanntIFilter
haben usw.). Im Allgemeinen würden Sie es in FP benennenfunc
oderf
wenn entwedersort
oderfilter
eine gültige Wahl ist! Zum Beispiel beim Schreiben einer Funktion höherer Ordnung, diefunc
als Parameter verwendet wird.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:
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.
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.
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.
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.
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).
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.
quelle
SafeString
Typ - Strings darzustellen , die HTML-entgangen waren - und in der Regel mehr Spur zu halten Auswirkungen.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.
quelle
Other
Variante / 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).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
und in Java sagst du
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
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.
quelle
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.
quelle
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
add
function 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.
quelle
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.