Was ist ein guter Weg, um große funktionale Programme zu entwerfen/strukturieren, insbesondere in Haskell?
Ich habe mir einige der Tutorials angeschaut (Write Yourself a Scheme ist mein Favorit, Real World Haskell steht an zweiter Stelle) - aber die meisten Programme sind relativ klein und für einen einzigen Zweck gedacht. Außerdem halte ich einige von ihnen nicht für besonders elegant (zum Beispiel die riesigen Lookup-Tabellen in WYAS).
Ich möchte jetzt größere Programme mit mehr beweglichen Teilen schreiben - Daten aus einer Vielzahl verschiedener Quellen erfassen, sie bereinigen, sie auf verschiedene Weise verarbeiten, sie in Benutzeroberflächen anzeigen, sie aufbewahren, über Netzwerke kommunizieren usw. Wie könnte man einen solchen Code am besten strukturieren, damit er lesbar, wartbar und an veränderte Anforderungen anpassbar ist?
Es gibt eine recht umfangreiche Literatur, die sich mit diesen Fragen für große objektorientierte, imperative Programme befasst. Ideen wie MVC, Design Patterns usw. sind gute Rezepte für die Verwirklichung breiter Ziele wie die Trennung von Belangen und Wiederverwendbarkeit in einem OO-Stil. Darüber hinaus eignen sich neuere imperative Sprachen für einen "Design as you grow"-Stil des Refactorings, für den Haskell meiner Meinung nach weniger gut geeignet ist.
Gibt es eine entsprechende Literatur für Haskell? Wie lässt sich der Zoo an exotischen Kontrollstrukturen, die in der funktionalen Programmierung zur Verfügung stehen (Monaden, Pfeile, applicative usw.), am besten für diesen Zweck nutzen? Welche Best Practices können Sie empfehlen?
Danke!
EDIT (dies ist eine Folgemaßnahme zu Don Stewarts Antwort):
@dons erwähnt: "Monaden fangen wichtige architektonische Designs in Typen ein."
Ich schätze, meine Frage ist: Wie sollte man über wichtige architektonische Designs in einer rein funktionalen Sprache nachdenken?
Betrachten Sie das Beispiel mit mehreren Datenströmen und mehreren Verarbeitungsschritten. Ich kann modulare Parser für die Datenströme in eine Reihe von Datenstrukturen schreiben, und ich kann jeden Verarbeitungsschritt als reine Funktion implementieren. Welche Verarbeitungsschritte für ein bestimmtes Datenelement erforderlich sind, hängt von seinem Wert und dem der anderen ab. Auf einige der Schritte sollten Nebeneffekte wie GUI-Aktualisierungen oder Datenbankabfragen folgen.
Was ist der "richtige" Weg, um die Daten und die Parsing-Schritte in einer schönen Weise zu verbinden? Man könnte eine große Funktion schreiben, die das Richtige für die verschiedenen Datentypen tut. Oder man könnte eine Monade verwenden, um zu verfolgen, was bisher verarbeitet wurde, und jeden Verarbeitungsschritt das, was er als nächstes braucht, aus dem Zustand der Monade holen lassen. Oder man könnte weitgehend getrennte Programme schreiben und Nachrichten herumschicken (diese Option gefällt mir nicht besonders).
Die Folien, die er verlinkt hat, enthalten einen Aufzählungspunkt "Dinge, die wir brauchen": "Idiome für die Abbildung von Design auf Typen/Funktionen/Klassen/Monaden". Was sind die Idiome? :)
9 Stimmen
Ich denke, der Kerngedanke beim Schreiben großer Programme in einer funktionalen Sprache ist kleine, spezialisierte und zustandslose Module, die über Nachrichtenübermittlung kommunizieren . Natürlich muss man sich ein wenig verstellen, denn ein echtes Programm braucht einen Zustand. Ich denke, hier glänzt F# gegenüber Haskell.
18 Stimmen
@Chaos, aber nur Haskell erzwingt standardmäßig Zustandslosigkeit, Sie haben keine Wahl und müssen hart arbeiten, um in Haskell einen Zustand einzuführen (um die Kompositionalität zu brechen) :-)
0 Stimmen
@Don - Ja, ich weiß, aber ich bin einer dieser Typen, die das Beste aus beiden Welten haben.
0 Stimmen
@caffeine - Auf den ersten Blick scheint es eine Menge Arbeit zu sein, aber Sie werden überrascht sein, wie großartig es ist, weitgehend getrennte Programme zu haben, die Nachrichten senden.
7 Stimmen
@ChaosPandion: Theoretisch habe ich nichts dagegen. In einer imperativen Sprache (oder einer funktionalen Sprache, die auf Message-Passing ausgelegt ist), würde ich das sicherlich tun. Aber Haskell hat andere Möglichkeiten, mit Zuständen umzugehen, und vielleicht lassen sie mich mehr von den "reinen" Vorteilen behalten.
1 Stimmen
Ich habe in diesem Dokument unter "Gestaltungsrichtlinien" ein wenig darüber geschrieben: community.haskell.org/~ndm/downloads/
0 Stimmen
"Was ist ein guter Weg, um große funktionale Programme zu entwerfen/strukturieren, insbesondere in Haskell?". In seiner Präsentation "Engineering Large Projects in Haskell" sagt Don Stewart, dass Galois bis zu 0,2MLOC Haskell-Programme entwickelt hat, was nach modernen Standards winzig ist (viele der weltweit größten Codebasen haben jetzt über 10MLOC!). In Anbetracht der Tatsache, dass Galois so ziemlich der einzige große industrielle Nutzer von Haskell in der Welt ist, würde ich sagen, dass niemand jemals ein großes Haskell-Programm entwickelt hat, also wird auch niemand wissen, wie man es strukturiert.
0 Stimmen
@ChaosPandion Beachten Sie, dass Sie in Haskell zum Beispiel mit unendlicher Rekursion oder Monaden Zustände erzeugen können.
5 Stimmen
@JonHarrop wir sollten nicht vergessen, dass MLOC zwar eine gute Metrik ist, wenn man Projekte in ähnlichen Sprachen vergleicht, aber für einen sprachübergreifenden Vergleich nicht viel Sinn macht, insbesondere bei Sprachen wie Haskell, wo die Wiederverwendung von Code und die Modularität im Vergleich zu anderen Sprachen viel einfacher und sicherer ist.
1 Stimmen
@tair: "Sprachen wie Haskell, wo Code-Wiederverwendung und Modularität viel einfacher und sicherer sind". Ich finde diese Aussage seltsam, wenn Haskell im Vergleich zu ML nur ein rudimentäres Modulsystem hat. Warum gibt es mindestens zwei >1MLOC OCaml-Codebasen in der Industrie (Jane St. und Citrix), aber keine in Haskell?
1 Stimmen
@JonHarrop Ich meinte nicht "Modularität" wie in Java, sondern eher, dass bei der Verwendung von Bibliotheken nicht viel Glue-Code oder optimierte Datenstrukturen benötigt werden. Vielleicht ist "Modularität" hier nicht das richtige Wort. Ich hatte keine GROSSEN Projekte in Haskell, aber ich war immer wieder erstaunt, wie wenig Code ich am Ende habe, wenn ich weiß, dass eine Bibliothek ihre Aufgaben höchstwahrscheinlich auf die effizienteste Weise erledigt. Ich denke, das meiste davon kommt von der Faulheit der Sprache. Ist OCaml faul?
0 Stimmen
@tair: OCaml ist nicht faul. Ich habe mehrere OSS-Haskell-Codebasen studiert und dachte nicht, dass Faulheit besonders vorteilhaft wäre.
1 Stimmen
Können Sie näher erläutern, wie "Monaden wichtige architektonische Entwürfe in Typen erfassen"?
0 Stimmen
Matt Parsons hat einen interessanten Blogbeitrag über Anwendungsdesign in Haskell geschrieben: parsonsmatt.org/2018/03/22/drei_schichten_haskell_kuchen.html