🔧 AI Nachrichten Major AI platforms go down in unprecedented simultaneous outage(03.09.2026 um 17:34 Uhr)
🔧 AI Nachrichten ChatGPT, Claude, and Grok Down? Users Report Widespread Outages(03.09.2026 um 19:14 Uhr)
🔧 AI Nachrichten OpenAI Launches GPT-6 Astra, Says We May Have Entered the AGI Era(03.09.2026 um 22:08 Uhr)
🔧 AI Nachrichten Claude Comes to CarPlay as Fifth Major AI Chatbot App(05.09.2026 um 05:31 Uhr)
🔧 AI Nachrichten OpenAI’s GPT-6 Astra Is AGI, Says NVIDIA CEO Jensen Huang(07.09.2026 um 06:31 Uhr)
🔧 AI Nachrichten Blame AI companies for Mac mini and Mac Studio shortage(31.08.2026 um 10:32 Uhr)
🔧 AI Nachrichten Major AI platforms go down in unprecedented simultaneous outage(03.09.2026 um 17:34 Uhr)
🔧 AI Nachrichten ChatGPT, Claude, and Grok Down? Users Report Widespread Outages(03.09.2026 um 19:14 Uhr)
🔧 AI Nachrichten OpenAI Launches GPT-6 Astra, Says We May Have Entered the AGI Era(03.09.2026 um 22:08 Uhr)
🔧 AI Nachrichten Claude Comes to CarPlay as Fifth Major AI Chatbot App(05.09.2026 um 05:31 Uhr)
🔧 AI Nachrichten OpenAI’s GPT-6 Astra Is AGI, Says NVIDIA CEO Jensen Huang(07.09.2026 um 06:31 Uhr)
🔧 AI Nachrichten Blame AI companies for Mac mini and Mac Studio shortage(31.08.2026 um 10:32 Uhr)

🔧 Programmierung 🕛 kürzlich 10 Min Lesezeit
0

The Monad Invasion - Part 3: Railway-Oriented Programming

↗ Quelle (dev.to)
🗣️ Stimme:
📑 Inhaltsübersicht

Hello friends,



Our previous post, "



It is strictly impossible to build software without handling errors. In the real world, failure can occur at any point during the execution, and we cannot ignore them.



What if we had the same workflow, written in an imperative style?




CODE
var myPhoneNumber = ...
var request = await StartVerificationRequest.Build().WithBrand("Monads Inc.").WithWorkflow(SmsWorkflow.Parse(myPhoneNumber)).Create();
var response = verifyClient.StartVerificationAsync(request);
var myCode = ... // Receive verification code based on the specified workflow
var verificationRequest = response.BuildVerificationRequest(myCode)
var result = verifyClient.VerifyCodeAsync(verificationRequest);
this.AuthenticationSuccessful();






It looks similar but something important is missing - error handling. Indeed, this snippet only shows the happy path, where everything goes as expected.



Of course, this is not enough - we aim for feature parity between these snippets. In its current state, any failure would cause our system to crash. As we previously mentioned, this flow can fail at various locations:




  • When building the authentication request

  • When processing the authentication request

  • When building the verification request

  • When processing the verification request



What's the difference if we decide to handle errors then?




CODE
var myPhoneNumber = ...
var request = await StartVerificationRequest.Build().WithBrand("Monads Inc.").WithWorkflow(SmsWorkflow.Parse(myPhoneNumber)).Create();
if (request.IsFailure)
{
// Can fail when Brand is invalid
// Can fail when PhoneNumber is invalid
return this.AuthenticationFailed(new Failure("Invalid input."));
}

var response = verifyClient.StartVerificationAsync(request);
if (response.IsFailure)
{
// Can fail when process can't be initiated
return this.AuthenticationFailed(new Failure("Cannot initiate verification process."));
}

var myCode = ... // Receive verification code based on the specified workflow
var verificationRequest = response.BuildVerificationRequest(myCode)
if (response.IsFailure)
{
// Can fail when the code input is invalid
return this.AuthenticationFailed(new Failure("Invalid code."));
}

var result = verifyClient.VerifyCodeAsync(verificationRequest);
if (result.IsFailure)
{
// Can fail when the verification fails
return this.AuthenticationFailed(new Failure("Verification failed."));
}

return this.AuthenticationSuccessful();









Note: This section shares resources from the

talk ".

More details are available on



In the example above, our workflow is composed of three steps:




  • Validate an input.

  • Update a record - if the input is valid.

  • Send a notification - if the record update is successful.



Here's what it looks like in an imperative fashion:




CODE
if (this.Validate(input))
{
try
{
var record = this.Update(input);
this.SendNotification(record);
}
catch (Exception)
{
...
}
}






We consider our workflow successful only if these three steps are successful.



We remain on that "Happy Path" until we face an error, which leads us to a "Failure Path" (Red). When this happens, we carry the failure until the end of the flow. It's crucial that we stick to the main path, as it's the only way to ensure the full execution of our workflow.





Does it ring a bell? What a coincidence, we use Monads for that! You didn't see that one coming - or did you?



In that allows us to authenticate for one of our Network APIs.



Can you find out what the railway tracks look like? Where can our workflow fail?




You don't need to know what's happening here; there's no logic. Focus on the flow.





CODE
public Task<Result<AuthenticateResponse>> AuthenticateAsync(Result<AuthenticateRequest> request) =>
request.Map(BuildAuthorizeRequest)
.BindAsync(this.SendAuthorizeRequest)
.Map(BuildGetTokenRequest)
.BindAsync(this.SendGetTokenRequest)
.Map(BuildAuthenticateResponse);






The answer is quite simple - our railway contains two points of branching:




  • When sending the authorize request.

  • When sending the get token request.



This is explicit because we rely on the Bind method for those operations.



For another comparison, here's the same code written in an imperative style:




CODE
public async Task<Result<AuthenticateResponse>> AuthenticateAsync(AuthenticateRequest request)
{
var authorizeRequest = BuildAuthorizeRequest(request);
var authorizeResponse = await this.SendAuthorizeRequest(authorizeRequest);
if (authorizeResponse.IsFailure)
{
return Result<AuthenticateResponse>.FromFailure(authorizeResponse.Failure);
}

var getTokenRequest = BuildGetTokenRequest(authorizeResponse.Success);
var getTokenResponse = await this.SendGetTokenRequest(getTokenRequest);
if (getTokenResponse.IsFailure)
{
return Result<AuthenticateResponse>.FromFailure(getTokenResponse.Failure);
}

return BuildAuthenticateResponse(getTokenResponse.Success);
}






As you can see, reading and understanding the flow is much easier than in the imperative style, and the function is smaller.

We achieved that by reducing the cognitive load of our method — reducing the required quantity of technical information (branching,

logic, variables, etc.) helps to make the code "fit in your head" (

see . We can picture it as a Result<TFailure, TSuccess.



See where I'm going?



Because of how generics work in C#, an instance of Result must always carry the same type of failure. In other terms, all our failures must have the same type - that's a problem as you may decide to treat a parsing failure differently than an API failure.



We cannot blame Monads or ROP for this issue - it is a language limitation. In comparison, this is where a language like F# shines, as it allows us to define a discriminated union type that can carry multiple types of failures. Still, hope is possible - discriminated unions will that all failures must implement. It allows us to group different failures (or reasons to fail - parsing, authentication, etc.) on the same failure path and define dedicated behaviours using the Type property.




CODE
public interface IResultFailure
{
/// <summary>
/// The type of failure.
/// </summary>
Type Type { get; }

/// <summary>
/// Returns the error message defined in the failure.
/// </summary>
/// <returns>The error message.</returns>
string GetFailureMessage();

/// <summary>
/// Converts the failure to an exception.
/// </summary>
/// <returns>The exception.</returns>
Exception ToException();

/// <summary>
/// Converts the failure to a Result with a Failure state.
/// </summary>
/// <typeparam name="T">The underlying type of Result.</typeparam>
/// <returns>A Result with a Failure state.</returns>
Result<T> ToResult<T>();
}






This is not a perfect solution and probably not the most elegant one. But this is similar to handling different types of exceptions, like the following example:




CODE
try { ... }
catch (ExceptionA) { ... }
catch (ExceptionB) { ... }
catch (ExceptionC) { ... }

// ---

switch (failure)
{
case HttpFailure httpFailure:
DoSomethingWithHttpFailure(httpFailure);
break;
case ResultFailure resultFailure:
DoSomethingWithResultFailure(resultFailure);
break;
default:
DoSomethingWithFailure(failure);
break;
}






As you can see, ROP allows us to use the same functionalities as exceptions - it's not a replacement, just an alternative to error handling which looks a bit different.






Conclusion



And there we have it — our third post in "The Monads Invasion" series. This time, we focused on Railway-Oriented Programming (ROP) to show how you can use Monads even further to handle errors.



The key takeaway? There's no loss when using Monads for error handling compared to a standard exception-based approach; everything you do using exceptions can be done with Monads. You'll likely find your code cleaner, easier to read, and more explicit about its intent.



Now, the real question: are you ready to try it?



I suggest starting small and easing into it. There's a learning curve like anything new, but stick with it, and you'll see the benefits.



If you have any questions or want to chat, feel free to hit me up on my or join us on

the . We're all in this together, and your voice matters.



Happy coding, and I'll catch you later!

Vollständiger Original-Bericht
Ausführliche Details, Code-Beispiele & Hersteller-Stellungnahme auf dev.to.
↗ Original-Artikel auf dev.to lesen
Wie bewertest du diesen Beitrag?
1 Klick Feedback
Teilen mit Netzwerk & Team:

Community-Analysen & Experten-Meinungen 0

Verfasse deine eigene Analyse, teile Workarounds oder diskutiere diesen Vorfall im Blog.
Noch keine Community-Analyse verfasst. Markiere einen Textabschnitt oder klicke oben auf Eigene Analyse verfassen“!
Community Pulse: Relevanz-Einschätzung
1 Klick Experten-Votum
🔴 Akute Relevanz 0%
🟡 In Evaluierung 0%
🟢 Keine Auswirkung 0%
Spannende Innovation 0%
Verwandte Story-Cluster & Quellen (Vektor-KI)
Port 8095 Engine
3 Quellen
GPT-6 Astra Release Today? OpenAI’s Next Major AI Model Is Almost Here
1 Quelle
Apple accuses OpenAI of destroying evidence as trade-secrets fight intensifies
1 Quelle
Major AI platforms go down in unprecedented simultaneous outage
Ähnliche Beiträge
🔍 Verwandte News

Auch interessante Nachrichten The Monad Invasion - Part 3: Railway-Oriented Programming

Thematisch verwandte Begriffe: Monad, Invasion, Part, RailwayOriented · 6 Treffer

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...