Railway-Oriented Programming
(fsharpforfunandprofit.com)- Railway-Oriented Programming ist ein Ansatz, um wiederkehrende Fehlerpfade in realen Anwendungen – etwa Validierung, Logging oder Netzwerkfehler – innerhalb einer funktionalen Pipeline zu behandeln
- Der Kern ist, die Grenzen von Beispielen zu überwinden, die nur erfolgreiche Pfade aneinanderreihen, und den Eisenbahnvergleich zu nutzen, damit auch F#-Nutzer den Fehlerfluss visuell leicht verstehen können
- Aus Haskell-Perspektive liegt es nahe an dem Typ
Eitherund an der Kleisli-Komposition, legt den Fokus aber eher auf Rezepte zur Fehlerbehandlung und Implementierungsmuster als auf abstrakte Theorie - Für die praktische Anwendung werden außerdem die F#-Bibliothek Chessie für NuGet, ein GitHub-Beispiel zum Vergleich von C# und F# sowie ein FizzBuzz-Beispiel bereitgestellt
- Es ist eine nützliche Methode, sollte aber nicht auf jedes Problem übermäßig angewendet werden; da F# keine type classes hat, definiert die Beispielbibliothek die benötigten Funktionen direkt selbst
Präsentationsmaterial und Beispielcode
- Die Seite zu Railway-Oriented Programming bündelt Folien und Code der Präsentation an einem Ort
- Ausgangspunkt ist das Problem, dass sich reale Anwendungen schwer mit nur dem in funktionalen Programmierbeispielen oft angenommenen „happy path“ bauen lassen
- Robuste Anwendungen müssen Validierung, Logging, Netzwerkfehler, Servicefehler und andere Ausnahmesituationen gemeinsam behandeln
- Das Ziel ist, mit dem Eisenbahnvergleich kurz und leicht verständlich zu zeigen, wie sich diese Fehlerbehandlung auf saubere funktionale Weise umsetzen lässt
- Ein ähnliches Thema wird auch in der Reihe recipe for a functional app fortgeführt
- Den eigentlichen Code gibt es im GitHub-Projekt, das ROP-Ansätze in C# und F# vergleicht
- Als Warnung, dass Fehlerbehandlung zwar nützlich ist, aber nicht extrem angewendet werden sollte, wird auch „Against Railway-Oriented Programming” als Referenz genannt
Video und Folien
- Dieses Thema wurde auf der NDC London 2014 präsentiert
- Weitere Videos derselben Präsentation sind ebenfalls verfügbar
- Es gibt Folien zur Präsentation vom 14. März 2014 bei der Functional Programming eXchange
- Die PowerPoint-Folien können auch auf GitHub heruntergeladen werden; dort steht außerdem, dass man sie frei ausleihen und verwenden darf
Der Either-Monad und Kleisli-Komposition
- Für Haskell-Nutzer könnte dieser Ansatz wie der Typ
Eitherwirken - Hier ist er auf eine Form spezialisiert, bei der der
Left-Fall eine Liste benutzerdefinierter Fehlertypen verwendet- Ein Beispieltyp ist
type TwoTrack a b = Either [a] (b,[a])
- Ein Beispieltyp ist
- Neu ist nicht das Konzept der Fehlerbehandlung selbst, sondern eher der Eisenbahnvergleich, mit dem es erklärt wird
- Warum hier keine standardmäßigen Haskell-Begriffe in den Vordergrund gestellt werden, ist klar
- Dieses Material ist kein Monad-Tutorial, sondern konzentriert sich auf das Lösen von Problemen der Fehlerbehandlung
- Viele Menschen, die mit F# anfangen, sind mit Monaden nicht vertraut; deshalb kann eine visuelle und weniger einschüchternde Erklärung intuitiver sein
- Einen Two-Track-Typ mit
bindsofort als Monade zu bezeichnen, wäre ungenau, und die Monadengesetze werden hier nicht behandelt Eitherist ein zu allgemeines Werkzeug; hier soll eher ein Rezept als das Werkzeug selbst vermittelt werden
Bausteine des Fehlerbehandlungsrezepts
- Dieser Ansatz ist ein umfassenderes Bündel von Techniken als bloß „verwende einfach
Eitherundbind“ - Zu den enthaltenen Techniken gehören:
- Verwendung von Listen benutzerdefinierter Fehlertypen auf linker und rechter Seite statt einer einfachen Form wie
Either String a - Einbindung monadischer Funktionen in die Pipeline mit
bind(>>=) - Komposition monadischer Funktionen mit Kleisli-Komposition (
>=>) - Einbindung nicht-monadischer Funktionen in die Pipeline mit
map(fmap) - Da F# keine IO-Monade verwendet, werden mit
teeFunktionen mitunitin die Pipeline integriert - Abbildung von Ausnahmen auf Fehlerfälle
- Parallele Kombination monadischer Funktionen mit
&&&, etwa für Validierungsszenarien - Nutzung der Vorteile benutzerdefinierter Fehlertypen im Domain-Driven Design
- Erweiterungen wie Logging, Domain Events und kompensierende Transaktionen
- Verwendung von Listen benutzerdefinierter Fehlertypen auf linker und rechter Seite statt einer einfachen Form wie
- Ziel ist ein Template, das vielseitig genug für fast alle Situationen ist und zugleich so eingeschränkt, dass es einen konsistenten Stil erzwingt
- Wenn sich die Art, wie Code geschrieben wird, praktisch auf eine Form vereinheitlicht, können spätere Wartende den Aufbau des Codes schneller verstehen
- Diese Methode ist nicht die einzige Lösung, kann aber ein guter Ausgangspunkt sein
- Selbst innerhalb der Haskell-Community ist die Fehlerbehandlung nicht einheitlich, was für Einsteiger verwirrend sein kann
Anwendung im eigenen Code
- Wer eine fertige F#-Bibliothek braucht, kann sich das Chessie project ansehen, das mit NuGet verwendet werden kann
- Ein Beispiel-Webservice mit dieser Technik wird als GitHub-Projekt bereitgestellt
- Ein Beispiel, das den ROP-Ansatz auf FizzBuzz anwendet, gibt es unter Railway Oriented Programming: carbonated
- F# hat keine type classes, daher gibt es keinen allgemeinen Weg, Monaden wiederzuverwenden
- Die FSharpX library bietet einen nützlichen Ansatz, aber die Bibliothek
Rop.fsdefiniert alle Funktionen von Grund auf selbst - Diese Isolation hat den Vorteil, dass es überhaupt keine externen Abhängigkeiten gibt
Weiterführende Lektüre
- Um Monaden korrekt zu definieren, reicht es nicht, einfach nur
bindundreturnzu implementieren - Monaden sind algebraische Strukturen, die die Monadengesetze erfüllen müssen; diese Gesetze hängen in bestimmten Situationen mit den Monoidgesetzen zusammen
- Wer sich für
Eitherund Kleisli-Komposition interessiert, kann die folgenden Materialien lesen- Monaden im Allgemeinen
- Die
Either-Monade - Kleisli-Kategorien und Komposition
- Umfassende Ansätze zur Fehlerbehandlung
2 Kommentare
Railway-Oriented Programming zur Entwicklung fehlerfreier Programme
Hacker-News-Kommentare
Elixir geht dieses Problem mit dem Keyword/Makro
withziemlich sauber an.Wie in
with {:ok, file_handle} <- File.open(filename), ... do {:ok, parsed} endwerden Funktionen von oben nach unten ausgeführt, und wenn ein Rückgabewert nicht zum Muster links passt, wird mit diesem Wert frühzeitig zurückgegeben.Dadurch muss man nicht jede Funktion so bauen, dass sie sowohl
{:ok, value}als auch{:error, reason}entgegennimmt; die Funktion kann nur den Wert bekommen, der sie interessiert, und das Pattern Matching imwith-Block übernimmt die Fehlerweitergabe.Wenn zum Beispiel
File.open{:error, reason}zurückgibt, wirdIO.readnicht ausgeführt, und das gesamte Ergebnis vonwithist{:error, reason}. Letztlich schreibt man nur den Erfolgspfad, und den Fehlerpfad kann der Aufrufer matchen, wenn er möchte.Es fühlt sich an, als würde man Exception Handling zur Hälfte neu erfinden; wenn man es statt
catcheinfachelsenennt, sieht es nur weniger nach Exception Handling aus.whereund Lispsletbeeinflusst.Im Grunde legt man damit Vorbedingungen einer Funktion fest, und in Elixir kann man zusätzlich sogar einen
else-Block verwenden.IO.readfehlschlägt, stellt sich außerdem die Frage, wer den File-Handle schließt.Der Autor schrieb einige Jahre später auch einen Folgebeitrag mit dem Titel Against Railway Oriented Programming: https://fsharpforfunandprofit.com/posts/against-railway-orie...
Railway Oriented Programming ist interessant und hat seine Einsatzgebiete, braucht meiner Ansicht nach aber einen großen Warnhinweis. In der Praxis habe ich oft gesehen, dass es dazu verwendet wird, Exception-Handling schlecht neu zu erfinden; wenn man Exceptions richtig versteht und einsetzt, lassen sich die meisten Fehlerbedingungen sauberer und effektiver behandeln.
Der Vorteil von Exceptions ist, dass in den meisten Fällen die sichere Wahl der Default ist. Eine Fehlerbedingung signalisiert, dass der Code nicht tun kann, was seine Spezifikation verlangt; wenn man in diesem Zustand weitermacht, können Daten aufgrund falscher Annahmen beschädigt werden. Exceptions räumen standardmäßig auf und propagieren dann zum Aufrufer, während Ansätze wie Railway Oriented Programming viel Boilerplate verlangen, die man leicht vergisst und leicht falsch macht.
Nützlich ist es allerdings in zwei Fällen. Der eine ist, wenn bestimmte, gut definierte erwartete Fehler – etwa bei Validierung – am Entstehungsort behandelt werden müssen; der andere sind Umgebungen, in denen man kein Exception-Handling verwenden kann. Früherer Promise-basierter asynchroner Code in jQuery war fast schon eine Implementierung von Railway Oriented Programming, aber mit modernem JavaScript-
async/awaitkann man wieder Exception-Handling verwenden.Der große Gewinn von Railway Oriented Programming ist, dass man schon an der Funktionssignatur sofort erkennen kann, welche Fehler auftreten können.
Boilerplate lässt sich je nach Sprache abmildern. Haskell hat die
do-Notation, und in F# kann man mit Result-Computation-Expressions sehr sauber arbeiten. Man kann zum Beispiel etwas wieLoginError = InvalidUser | InvalidPwd | Unauthorized of AuthErrordefinieren und den Ablauf mitResult.requireSome,Result.requireTrueundResult.mapErrorzusammensetzen.Umgekehrt frage ich mich, ob sich auch die Nachteile von Exceptions abmildern lassen. Ich wüsste gern, ob es Linter oder statische Analysewerkzeuge gibt, die in eine Java-IDE integriert sind und automatisch anzeigen, welche ungefangenen Exceptions von einer bestimmten Codezeile geworfen werden können.
https://demystifyfp.gitbook.io/fstoolkit-errorhandling/fstoo...
Zunächst erzwingen viele Sprachen einen umständlichen und unnötigen Scope, um Exceptions zu fangen. Wenn man eine Variable als Ergebnis einer Funktion deklarieren und initialisieren will, die eine Exception werfen kann, und ihr im Fehlerfall einen anderen Wert geben möchte, muss man Deklaration und Initialisierung trennen.
Das größere Problem ist, dass Fehlerbedingungen auf der niedrigsten Ebene behandelt werden sollten, auf der es genügend Kontext gibt, um sie korrekt zu behandeln, während der Default bei Exceptions lautet: „bis ganz nach oben werfen“. Fügt man zum Beispiel einen Datei-Cache hinzu, kann die übergeordnete Funktion
HandleRequestplötzlich I/O-Exceptions werfen; so lecken Abstraktionen leicht.Alles, was eine Funktion zurückgeben kann, sollte explizit Teil ihrer Signatur sein, und der Aufrufer sollte es entweder explizit behandeln oder zumindest kennzeichnen müssen, dass er es nach oben weiterreicht. Die Sprache muss dafür nicht viel Boilerplate verlangen.
Result-Typ/-Traits Fehlerbehandlung richtig hinbekommen haben.Auch wenn eine Datei oder ein Datensatz fehlt, hängt es davon ab, ob das erwartet ist. Wenn es eine Anfrage ist, die anhand einer vom Benutzer angegebenen ID nachschlägt, ist diese Abfrage selbst eine Validierung der Benutzereingabe. Wenn man aber anhand eines in einem Datenbankdatensatz gespeicherten Blob-Namens im Storage nachschlägt und der Blob fehlt, ist das eine Situation, die sich gut als Exception behandeln lässt.
Die Grenze zwischen Fehlern und Exceptions ist unscharf und hängt vom Team und vom Problembereich ab, aber als Faustregel finde ich das ganz brauchbar.
In C++-Code kann man Daten früh prüfen, bei fehlgeschlagener Prüfung die Anwendung abstürzen lassen und dann anhand des gefundenen Fehlerzustands den frühen Prüfcode debuggen und korrigieren.
Da es bei den verarbeiteten Daten um relativ klar umrissene Daten geht, die einem bekannten CAD-Datenformat oder einer geometrischen Topologie folgen, ist der Umgang mit Fehlerbedingungen insofern „ziemlich einfach“, als man zuerst verstehen kann, was korrekte Daten sind.
Ehrlich gesagt ist diese ganze Site eine Schatzkammer. Selbst wenn man nicht vorhat, funktionale Sprachen zu verwenden, vermittelt sie andere Perspektiven, und das hat mir persönlich sehr geholfen. Ich empfehle auch die anderen Beiträge.
Trotzdem werde ich wohl nie verstehen, was eine Monade ist.
https://fsharpforfunandprofit.com/series/understanding-parse...
Ich frage mich, ob die Behauptung „Es ist schlecht, Fehlerbehandlung weit weg von der Aufrufstelle zu schieben“ haltbar ist. Da der Aufrufer Fehler am besten behandeln kann, sollte er sie nicht eher selbst behandeln, statt sie nach unten weiterzureichen?
Im Ablauf
validate and-then update-db and-then send-emailkann man ein Scheitern vonvalidatezum Beispiel durchaus an den Aufrufer zurückgeben. Wenn aberupdate-dbfehlschlägt, muss entschieden werden, ob erneut versucht wird, ein anderer Dienst genutzt wird, der Auftrag wieder in eine Queue gelegt wird oder der Nutzer benachrichtigt wird. Bei einem Fehlschlag vonsend-emailist es genauso.Am Ende entsteht doch eine komplexere Verzweigungsstruktur:
ValidationErrordem Nutzer melden,DBErrornach 5 Minuten erneut versuchen oder nach einem Remote-Retry melden,EmailFailedwieder in die E-Mail-Queue legen und als Erfolg betrachten. Vielleicht ist das aber auch in Ordnung.Leute, die Go nicht mögen, beschweren sich, dass man Fehlerbedingungen zu häufig prüfen muss; bei Javas checked Exceptions ist es ähnlich. Mit
Eitherwird man nicht zum Prüfen gezwungen, sondern kann selbst prüfen oder an den Aufrufer weiterreichen.Leute, die Javas unchecked Exceptions nicht mögen, beschweren sich, dass man nicht weiß, was wann geworfen wird. Mit
Eitherwird das explizit.Der Kern ist, dass man nicht weiß, was bei einem Fehler zu tun ist, und
Eitherpasst gut zu diesem Nichtwissen. In einer aktuellen Java-Codebasis könnteLimit getUserLimit(User)bei Nichtexistenz eineNotFound-Exception werfen,nullzurückgeben oder einen Default-Wert zurückgeben; bevor man sich durch den Code gegraben hat, weiß man es nicht.Mit
Eitherkönnte der Aufrufer sich darauf verlassen, ohne den internen Code zu lesen, und mit eingebauten Werkzeugen wieorElse(null)oderorElseThrow(...)leicht in andere Fehlerbedingungen umwandeln. Außerdem istEitherein Ausdruck und lässt sich gut abstrahieren, sodass man statt spezieller Retry-Logik innerhalb vonDbUpdaterallgemeine Retry-Logik schreiben kann.try-catch-Stil von Java umzusetzen, aber verschachteltetrys,catch(DBError),sleep(5), Remote-Retry, erneutes Queuen beiEmailFailedErrorund Benachrichtigung beiValidationErrorgeraten durcheinander und sehen deutlich schlechter aus.https://gist.github.com/Andrewp2/9d97bd213b061166d6df565ce26...
Jemand könnte sicher eine bessere
try-catch-Version schreiben, aber ich finde sie deutlich schlechter als die vorgestellte Railway-Version.Ich mag DDD, aber gewöhnliche Programmierer wie ich brauchen einen vernünftigen Wartungszyklus, den sie bewältigen können. TDD war bei veränderbarem Code deshalb gut, weil es die kognitive Last der Wartung reduziert hat.
Railway-orientierte Programmierung kann über viele Transformationen hinweg viel Kontext mittragen, und ich möchte vermeiden, am Ende der Schiene einen Zug voller noch nicht aufbereiteter Geschäfts- und Systemkontexte entgegenzunehmen.
Ich denke, DDD und Onion Architecture bieten die Vorteile beider Seiten. Am Ende gibt es zwar eine Railway, aber sie steckt nicht im Inneren einer Micro-App, sondern liegt außen. Railway-artige Themen sollten besser explizit und etwas umständlich codiert werden; wenn man es richtig macht, sind die meisten Railway-Entscheidungen Geschäftsentscheidungen, die man nicht durch clevere Code-Strukturen vermeiden sollte.
Monadische Programmierung ist großartig, aber sie verführt dazu, notwendige Geschäftsentscheidungen zu umgehen. Wenn man Mehrdeutigkeit ins Typsystem packt, kann das später zu schwierigen oder unmöglichen Situationen führen.
try/catchinnerhalb eines großentry/catch.Zum Beispiel kann man sich dafür entscheiden, innerhalb der Funktion
send-emailsehr schnelle transiente Fehler zu behandeln. Wenn innerhalb der zulässigen Zeit eine Verbindung zustande kommt, kehrt man auf den Erfolgspfad zurück; andernfalls gibt man es an den Aufrufer zurück.Wenn es eine klare Regel gibt, dann diese: Verarbeitung, die menschliches Eingreifen erfordert, muss unbedingt verpackt und nach oben weitergereicht werden. Selbst wenn man auf den Erfolgspfad zurückkehrt, sollte man besser ein Warn-Log hinterlassen.
notify-userübernimmt nutzerspezifische Benachrichtigungen fürValidationErrorundDBError,try-update-dbbündeltupdate-db, den Retry nach 5 Minuten und den Remote-Retry, undtry-send-emailkann bei einem Fehlschlag die E-Mail wieder in die Queue legen und in einen Erfolg umwandeln.Dann bleibt die oberste Ebene etwa
validate and-then try-update-db and-then try-send-email or-then notify-user. Ich glaube,and/or/thenwurde gut aufmap/bindübertragen.Diese Website war von allen Programmier-Lernseiten, die ich kennengelernt habe, die beste darin, reale und praktische Konzepte zu vermitteln. Besonders gefällt mir das Konzept, ungültige Zustände nicht darstellbar zu machen, und ich versuche, es unabhängig von der Sprache anzuwenden. Natürlich machen manche Sprachen das einfacher.
https://fsharpforfunandprofit.com/posts/designing-with-types...
Wenn man statt Aufruf/Rückgabe Datenfluss verwendet, lässt sich dieses Problem besser lösen, und zu einem großen Teil verschwindet es von selbst.
Bei Aufruf/Rückgabe muss etwas zurückgegeben werden; wenn es wegen eines Fehlers keinen Rückgabewert gibt, muss man also den Fehler zurückgeben oder, wie in Go, Fehler und regulären Rückgabewert zusammen zurückgeben. Das verunreinigt den Erfolgspfad. Wenn man die weitere Verarbeitung über einen polymorphen Container durchreicht, wird es etwas besser, aber das Problem bleibt bestehen.
Im Datenfluss schickt man einfach nichts an die nächste Filterstufe, sodass der Erfolgspfad überhaupt nicht beeinflusst wird. Fehler kann man an eine Stelle wie die Standardfehlerausgabe schicken und auf Anwendungsebene zentralisieren.
Das mag zu gut klingen, aber wir haben es bei Wunderlist tatsächlich eingesetzt, und es funktionierte gut. Dieselbe Technik für den Fall, dass kein Wert vorhanden ist, lässt sich unverändert auch dann anwenden, wenn noch kein Wert vorhanden ist, also bei asynchroner Verarbeitung.
Wenn in einem einfachen Datenstrom ein Fehler auftritt, ist es in Ordnung, anzuhalten, weil es keine Daten für den nächsten Schritt gibt. Wenn man aber bei
sqrt(-1)sofort „crasht“ und zur Fehlerausgabe oder zum Logging springt, wird es in bestimmten Anwendungsfällen schwierig, wenn man die Domäne erweitern möchte, sodass für negative Zahlen ein anderer Wert geliefert wird.Hier sind explizite Fehlertypen wie
Maybenützlich. Den Erfolgspfad schreibt man so, als gäbe es keinen Fehler, verwendet für die Funktionskomposition aber einen anderen Operator. Wenn man sich von einem Fehler erholen möchte, behandelt man an dieser Stelle beide Pfade; danach kann man Fehler eventuell nicht mehr zulassen. Besonders gut daran ist, dass man beide Pfade elegant behandeln muss, wenn man aus einem abstrakten „möglicherweise vorhandenen Wert“ den tatsächlichen Wert herausziehen will.Mit expliziten Fehlertypen kommt man auch über die Dichotomie Erfolg/Fehler hinaus. Man kann Zustände wie „kein Wert, aber …“ oder „ein Wert, aber …“ ausdrücken und bei mehreren parallel verarbeitbaren Aufgaben Fehler akkumulieren und als Ergebnis sammeln. Darüber hinaus kann man Nichtdeterminismus codieren und ihn etwa für Graphdurchläufe verwenden.
Man kann, wie Haskell-Programmierer es tun, Zustand auf reine Weise mitführen; und die berüchtigte
IO-Monade von Haskell ist weniger eine Darstellung des tatsächlichen Pfads als eher ein Mechanismus, der die Berührungspunkte mit der Außenwelt aufmainbeschränkt. Das dient der Reinheit, aber ich finde, Reinheit ist nicht immer ein absolut anzustrebender Wert.Diese Technik ist weit vielfältiger als eine einfache Eisenbahn und bietet eine saubere Möglichkeit, unter Wahrung der Reinheit nur den Erfolgspfad zu schreiben. Sie ist auch vorteilhaft für gleichungsbasierte statische Analyse und kopflose Unit-Tests. Ich wünschte, mehr Mainstream-Sprachen würden diesen Monaden-Werkzeugkasten besser bereitstellen.
Hier sind verwandte Links: Railway Oriented Programming - https://news.ycombinator.com/item?id=31404643 - Mai 2022, Railway-Oriented Programming (2015) - https://news.ycombinator.com/item?id=17337155 - Juni 2018, Railway oriented programming - https://news.ycombinator.com/item?id=11955917 - Juni 2016, Railway Oriented Programming - https://news.ycombinator.com/item?id=9166943 - März 2015, Railway-oriented programming - https://news.ycombinator.com/item?id=7887134 - Juni 2014
Zusätzlich: What is railway oriented programming? (2020) - https://news.ycombinator.com/item?id=34245639 - Januar 2023, Railway Oriented Programming in Elixir (2015) - https://news.ycombinator.com/item?id=11958578 - Juni 2016
Dieser Stil ist wie andere Monaden ansteckend, sodass die gesamte Business-Logik diese Form annimmt. Wenn das für dich in Ordnung ist, kannst du weiterlesen.
Exceptions sind, wie der Name sagt, unerwartete Fehler; sie spulen den Stack bis zu der Stelle zurück, an der sie abgefangen werden, und nötige Aufräumarbeiten werden automatisch erledigt. Sie eignen sich für Fehler, die außerhalb des Geltungsbereichs einer Business-Funktion auftreten, etwa I/O-Fehler oder Speichermangel.
Wenn du Daten validieren willst, musst du dich nicht auf Exception-Handler oder Monaden verlassen. Mach einfach das Ergebnis der funktionalen Pipeline zu Daten. Wenn es Validierungsfehler gibt, sammle sie in einer Menge und prüfe sie nach dem Ende der Pipeline. Meistens will man wahrscheinlich nicht beim ersten Fehler scheitern.
Wenn du in der Pipeline Seiteneffekte brauchst: tu es nicht. Beschreibe diesen Effekt stattdessen als Daten und führe ihn später aus. Wenn du unterwegs früh aussteigen musst, teile die Pipeline an dieser Stelle, verarbeite das Ergebnis und gib es dann in eine andere Pipeline.
Oder du kannst eine Interceptor-Chain verwenden, die entscheidet, ob der Erfolgspfad fortgesetzt wird. Wenn die Sprache es unterstützt, kannst du Pattern Matching verwenden. Entscheidend ist, dass die Entscheidung, ob ein Teil der Pipeline übersprungen wird oder nicht, außerhalb der Business-Funktion getroffen wird; dann kümmert sich die Business-Funktion nur um reine Datentransformation und wird dadurch besser wiederverwendbar.
Es ist schon gut, dass so ein Thema wieder auftaucht und diskutiert wird. Ich habe diese Website ein Jahr lang gelesen und die Beispiele nachvollzogen; sie war eines der besten F#-Materialien
Schon die Konzepte, die auf dieser Website behandelt werden, haben mir geholfen, in allen Sprachen funktional zu denken
Die Website ist alt, aber wenn sie jetzt Aufmerksamkeit bekommt, frage ich mich, ob das bedeutet, dass F# an Fahrt gewinnt. Und ich frage mich, ob Programmierer anderer Sprachen ebenfalls eine eisenbahnartige Fehlerbehandlung wie in Rust verwenden
Trotzdem kann ich nicht zu F# wechseln, weil ich die meiste Zeit mit dem Anpassen von Desktop-Frontends verbringe. In diesem Bereich scheint F# kaum Vorteile zu bieten. Produktionsreife Desktop-Frontends bestehen meist nicht aus nativem F#, sondern aus C#-basiertem objektorientiertem Code und werden nur über dünnen Glue Code mit F# verbunden
Sobald auch nur etwas komplexere UI-Anpassungen nötig werden, muss man zurück in die objektorientierte Welt, und dann finde ich es besser, es direkt in C# zu machen. Meiner Meinung nach braucht F# ein natives UI-Framework, um an Fahrt zu gewinnen
Tund einem bestimmten Enum parametrisiert werden kann, das die Ergebnisbedingungen definiertWenn es schnell gehen muss, kann man auch
std::pairverwenden, oder Fehlermeldungen als String zurückgeben und frühzeitig abbrechen, wenn der String nicht leer ist. Wenn alle Funktionen so einen Typ zurückgeben, lassen sich Fehlermeldungen bis ganz nach oben hochreichenIch finde es gut, dass dieser Artikel alle paar Jahre wieder auftaucht. Er ist wirklich ein hervorragender Artikel und Vortrag
Interessant ist auch, dass ich mich bei jedem erneuten Lesen auf meiner technischen Reise in Bezug auf Können oder Philosophie an einem anderen Punkt befinde und er mir deshalb jedes Mal anders erscheint