it-swarm.com.de

Gibt es eine Software-Engineering-Methodik für die funktionale Programmierung?

Software Engineering, wie es heute gelehrt wird, konzentriert sich ausschließlich auf objektorientierte Programmierung und die "natürliche" objektorientierte Sicht auf die Welt. Es gibt eine detaillierte Methode, die beschreibt, wie ein Domänenmodell in ein Klassenmodell mit mehreren Schritten und vielen (UML-) Artefakten wie Anwendungsfalldiagrammen oder Klassendiagrammen umgewandelt wird. Viele Programmierer haben diesen Ansatz verinnerlicht und haben eine gute Idee, wie eine objektorientierte Anwendung von Grund auf neu erstellt werden kann.

Der neue Hype ist die funktionale Programmierung, die in vielen Büchern und Tutorials gelehrt wird. Was ist aber mit funktionalem Software-Engineering? Beim Lesen von LISP und Clojure bin ich auf zwei interessante Aussagen gestoßen:

  1. Funktionsprogramme werden oft von unten nach oben statt von oben nach unten entwickelt ('On LISP', Paul Graham)

  2. Funktionale Programmierer verwenden Maps, in denen OO-Programmierer Objekte/Klassen verwenden ('Clojure for Java Programmers', Vortrag von Rich Hickley).

Wie sieht also die Methodik für ein systematisches (modellbasiertes?) Design einer funktionalen Anwendung aus, d. H. In LISP oder Clojure? Was sind die allgemeinen Schritte, welche Artefakte verwende ich, wie ordne ich sie vom Problemraum zum Lösungsraum zu?

200
Thorsten

Gott sei Dank, dass die Leute von der Softwareentwicklung die funktionale Programmierung noch nicht entdeckt haben. Hier sind einige Parallelen:

  • Viele OO "Entwurfsmuster" werden als Funktionen höherer Ordnung erfasst. Beispielsweise wird das Besuchermuster in der Funktionswelt als "Falz" bezeichnet (oder wenn Sie ein Theoretiker mit spitzen Köpfen sind) , ein "Katamorphismus"). In funktionalen Sprachen sind Datentypen meist Bäume oder Tupel, und mit jedem Baumtyp ist ein natürlicher Katamorphismus verbunden.

    Diese Funktionen höherer Ordnung unterliegen häufig bestimmten Programmiergesetzen, auch "freie Theoreme" genannt.

  • Funktionale Programmierer verwenden Diagramme viel seltener als OO Programmierer. Vieles, was in OO Diagrammen ausgedrückt wird, wird stattdessen in ausgedrückt. types oder in "Signaturen", die Sie sich als "Modultypen" vorstellen sollten. Haskell verfügt auch über "Typklassen", die einem Schnittstellentyp ähneln.

    Diejenigen funktionalen Programmierer, die Typen verwenden, denken im Allgemeinen, dass "der Code sich selbst schreibt, sobald Sie die richtigen Typen gefunden haben".

    Nicht alle funktionalen Sprachen verwenden explizite Typen, aber das Buch How To Design Programs , ein ausgezeichnetes Buch zum Lernen von Scheme/LISP/Clojure, stützt sich stark auf "Datenbeschreibungen", die eng miteinander verbunden sind im Zusammenhang mit Typen.

Wie sieht also die Methodik für ein systematisches (modellbasiertes?) Design einer funktionalen Anwendung aus, d. H. In LISP oder Clojure?

Jede auf Datenabstraktion basierende Entwurfsmethode funktioniert gut. Ich denke, dass dies einfacher ist, wenn die Sprache explizite Typen hat, aber es funktioniert auch ohne. Ein gutes Buch über Entwurfsmethoden für abstrakte Datentypen, das sich leicht an die funktionale Programmierung anpassen lässt, ist Abstraction and Specification in Program Development von Barbara Liskov und John Guttag , die erste Ausgabe. Liskov gewann den Turing-Preis zum Teil für diese Arbeit.

Eine andere für LISP einzigartige Entwurfsmethode besteht darin, zu entscheiden, welche Spracherweiterungen in der Problemdomäne, in der Sie arbeiten, nützlich sind, und diese Konstrukte dann mithilfe von Hygienemakros zu Ihrer Sprache hinzuzufügen. Ein guter Ort, um über diese Art von Design zu lesen, ist Matthew Flatts Artikel Erstellen von Sprachen in Rackets . Der Artikel befindet sich möglicherweise hinter einer Paywall. Sie können auch allgemeineres Material zu dieser Art von Design finden, indem Sie nach dem Begriff "domänenspezifische eingebettete Sprache" suchen. Für besondere Ratschläge und Beispiele, die über das hinausgehen, was Matthew Flatt behandelt, würde ich wahrscheinlich mit Grahams über LISP oder beginnen vielleicht ANSI Common LISP .

Was sind die üblichen Schritte, welche Artefakte verwende ich?

Allgemeine Schritte:

  1. Identifizieren Sie die Daten in Ihrem Programm und die Operationen darauf und definieren Sie einen abstrakten Datentyp, der diese Daten darstellt.

  2. Identifizieren Sie allgemeine Aktionen oder Berechnungsmuster und drücken Sie sie als Funktionen oder Makros höherer Ordnung aus. Erwarten Sie diesen Schritt als Teil des Refactorings.

  3. Wenn Sie eine typisierte funktionale Sprache verwenden, verwenden Sie die Typprüfung früh und häufig. Wenn Sie LISP oder Clojure verwenden, besteht die beste Vorgehensweise darin, zuerst Funktionsverträge einschließlich Komponententests zu schreiben - dies ist eine testgetriebene Entwicklung mit maximaler Effizienz. Und Sie möchten die Version von QuickCheck verwenden, die auf Ihre Plattform portiert wurde. In Ihrem Fall heißt sie ClojureCheck . Es ist eine äußerst leistungsfähige Bibliothek zum Erstellen von zufälligen Code-Tests, die Funktionen höherer Ordnung verwenden.

161
Norman Ramsey

Für Clojure empfehle ich, zur guten alten relationalen Modellierung zurückzukehren. Out of the Tarpit ist eine inspirierende Lektüre.

44
cgrand

Persönlich stelle ich fest, dass alle üblichen guten Praktiken aus der OO=) - Entwicklung auch für die funktionale Programmierung gelten - nur mit ein paar kleinen Änderungen, um die funktionale Weltanschauung zu berücksichtigen. Ich muss wirklich etwas grundlegend anderes tun.

Meine Erfahrung stammt aus dem Umzug von Java nach Clojure in den letzten Jahren.

Einige Beispiele:

  • Verstehen Sie Ihre Geschäftsdomäne/Ihr Datenmodell - Ebenso wichtig, ob Sie ein Objektmodell entwerfen oder eine funktionale Datenstruktur mit verschachtelten Maps erstellen. In mancher Hinsicht kann FP einfacher sein, da Sie dazu angeregt werden, das Datenmodell getrennt von Funktionen/Prozessen zu betrachten, aber Sie müssen immer noch beides tun.

  • Serviceorientierung im Design - funktioniert eigentlich sehr gut aus einer FP Perspektive, da ein typischer Service wirklich nur eine Funktion mit einigen Nebenwirkungen ist. Ich denke, dass der "Boden" up "Ansicht der Software-Entwicklung manchmal in der LISP-Welt vertreten ist eigentlich nur gute Service-orientierte API-Design-Prinzipien in einem anderen Gewand.

  • Test Driven Development - funktioniert gut in FP Sprachen, manchmal sogar noch besser, weil sich reine Funktionen hervorragend dazu eignen, klare, wiederholbare Tests zu schreiben, ohne dass dafür Einstellungen erforderlich sind Sie können auch separate Tests erstellen, um die Datenintegrität zu überprüfen (z. B. enthält diese Karte alle erwarteten Schlüssel, um die Tatsache auszugleichen, dass in einer OO Sprache die Klassendefinition würde dies zur Kompilierungszeit für Sie erzwingen.

  • Prototying/Iteration - funktioniert genauso gut mit FP. Sie könnten sogar in der Lage sein, Prototypen mit Benutzern zu erstellen, wenn Sie sehr gut darin sind, Tools/DSL zu erstellen und diese auf der REPL zu verwenden.

38
mikera

OO-Programmierung koppelt Daten eng mit Verhalten. Die funktionale Programmierung trennt die beiden. Sie haben also keine Klassendiagramme, aber Datenstrukturen und insbesondere algebraische Datentypen. Diese Typen können so geschrieben werden, dass sie sehr genau zu Ihrer Domain passen, einschließlich der Eliminierung unmöglicher Werte durch Konstruktion.

Es gibt also keine Bücher und Bücher darüber, aber es gibt einen gut etablierten Ansatz, um, wie das Sprichwort sagt, unmögliche Werte nicht darstellbar zu machen.

Auf diese Weise können Sie eine Reihe von Auswahlmöglichkeiten für die Darstellung bestimmter Datentypen als Funktionen und umgekehrt für die Darstellung bestimmter Funktionen als Vereinigung von Datentypen treffen, sodass Sie z. B. Serialisierung, genauere Spezifikation, Optimierung usw .

Dann schreiben Sie Funktionen über Ihre Anzeigen, so dass Sie eine Art Algebra - aufstellen, d. H. Es gibt feste Gesetze, die für diese Funktionen gelten. Einige sind möglicherweise idempotent - das gleiche nach mehreren Anwendungen. Einige sind assoziativ. Einige sind transitiv usw.

Jetzt haben Sie eine Domain, über die Sie Funktionen haben, die sich nach gut verhaltenen Gesetzen zusammensetzen. Ein einfaches eingebettetes DSL!

Oh, und gegebene Eigenschaften, Sie können natürlich automatisierte zufällige Tests von ihnen schreiben (ala QuickCheck) .. und das ist nur der Anfang.

13
sclv

Objektorientiertes Design ist nicht dasselbe wie Software-Engineering. Das Software-Engineering hat mit dem gesamten Prozess zu tun, wie wir von den Anforderungen zu einem funktionierenden System rechtzeitig und mit einer geringen Fehlerrate gelangen. Die funktionale Programmierung unterscheidet sich zwar von OO, sie beseitigt jedoch nicht die Anforderungen, die anspruchsvollen und detaillierten Entwürfe, die Überprüfung und das Testen, die Softwaremetriken, die Schätzung und all das andere "Software-Engineering-Zeug".

Darüber hinaus weisen Funktionsprogramme Modularität und andere Strukturen auf. Ihre detaillierten Entwürfe müssen anhand der Konzepte in dieser Struktur ausgedrückt werden.

7
Kaz

Siehe meine Antwort auf einen anderen Beitrag:

Wie geht Clojure mit der Trennung von Bedenken um?

Ich bin damit einverstanden, dass mehr zum Thema Strukturierung großer Anwendungen geschrieben werden muss, die einen FP -Ansatz verwenden.

5
drcode

Ein Ansatz besteht darin, ein internes DSL innerhalb der funktionalen Programmiersprache der Wahl zu erstellen. Das "Modell" ist dann ein Satz von Geschäftsregeln, die in der DSL ausgedrückt werden.

5
James Kingsbery

Obwohl dies als naiv und simpel angesehen werden kann, denke ich, dass "Designrezepte" (ein systematischer Ansatz zur Problemlösung, der auf die Programmierung angewendet wird, wie von Felleisen et al. In ihrem Buch HtDP ) nahe an Ihren Vorstellungen liegt scheinen zu suchen.

Hier ein paar Links:

http://www.northeastern.edu/magazine/0301/programming.html

http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.86.8371

3

Ich habe kürzlich dieses Buch gefunden: Functional and Reactive Domain Modeling

Ich denke, das passt perfekt zu Ihrer Frage.

Aus der Buchbeschreibung:

Funktionale und reaktive Domänenmodellierung zeigt Ihnen, wie Sie das Domänenmodell in Form von reinen Funktionen betrachten und diese zusammensetzen, um größere Abstraktionen zu erstellen. Sie beginnen mit den Grundlagen der funktionalen Programmierung und gelangen schrittweise zu den fortgeschrittenen Konzepten und Mustern, die Sie zur Implementierung komplexer Domänenmodelle benötigen. Das Buch zeigt, wie fortgeschrittene FP Muster wie algebraische Datentypen, typenbasiertes Design und das Isolieren von Nebenwirkungen Ihr Modell für die Lesbarkeit und Überprüfbarkeit komponieren können.

2
elviejo79

Ich habe festgestellt, dass behaviour Driven Development eine natürliche Lösung für die schnelle Entwicklung von Code in Clojure und SBCL ist. Der wahre Vorteil der Nutzung von BDD mit einer funktionalen Sprache besteht darin, dass ich tendenziell viel feinere Körnungstests schreibe, als ich es normalerweise mit prozeduralen Sprachen tue, da ich das Problem viel besser in kleinere Funktionsblöcke zerlegen kann.

2
Marc

Es gibt den Stil "Programmberechnung"/"Design by Calculation", der mit Prof. Richard Bird und der Algebra of Programming-Gruppe an der Universität Oxford (UK) assoziiert ist.

Persönlich mag ich die Arbeit der AoP-Gruppe, aber ich habe nicht die Disziplin, Design auf diese Weise selbst zu üben. Das ist jedoch mein Manko und nicht eines der Programmberechnung.

1
stephen tetley

Um ehrlich zu sein, wenn Sie Rezepte für Funktionsprogramme entwerfen möchten, werfen Sie einen Blick auf die Standardfunktionsbibliotheken wie Haskells Prelude. In FP werden Muster normalerweise von Prozeduren höherer Ordnung (Funktionen, die Funktionen bearbeiten) selbst erfasst. Wenn also ein Muster erkannt wird, wird häufig einfach eine Funktion höherer Ordnung erstellt, um dieses Muster zu erfassen.

Ein gutes Beispiel ist fmap. Diese Funktion nimmt eine Funktion als Argument und wendet sie auf alle "Elemente" des zweiten Arguments an. Da es Teil der Functor-Typklasse ist, kann jede Instanz eines Functors (z. B. eine Liste, ein Diagramm usw.) als zweites Argument an diese Funktion übergeben werden. Es erfasst das allgemeine Verhalten beim Anwenden einer Funktion auf jedes Element des zweiten Arguments.

1
nightski