Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Question] More elegant solution than nested Match #195

Open
Hiranus opened this issue Feb 12, 2025 · 1 comment
Open

[Question] More elegant solution than nested Match #195

Hiranus opened this issue Feb 12, 2025 · 1 comment

Comments

@Hiranus
Copy link

Hiranus commented Feb 12, 2025

I am dealing with parsing and validating xml and I wanted to avoid throw, so I decided to try OneOf, but how do I correctly handle multiple nested OneOf.Match() to not make them look like a ladder?

For example this - each call can result in XmlException being returned instead of whatever I want.

ValidationResult ValidateXML (string xml)
{
    XDocument doc = ParseIntoXDoc(xml);
    Version ver = ExtractVersion(doc);
    return ValidateXML(ver, doc);
}

Normally I would just have if after everything to check if I got null due to whatever reason, but OneOf simplifies it to this:

ValidationResult ValidateXML (string xml)
{
    return ParseIntoXDoc(xml).Match(
        ex => ValidationResult(ex),
        doc => ExtractVersion(doc).Match(
            ex => ValidationResult(ex),
            ver => ValidateXML(ver, doc);
        ));
}

After a few more checks like above it will look like if "ladder" and I really dislike that. Is there a more elegant solution to this?

@emperador-ming
Copy link

emperador-ming commented Feb 13, 2025

What you're looking for is called Monadic Binding.

There are several approaches you can take:

Create a pseudo-monadic Bind extension yourself

using OneOf;

public static class OneOfExtensions
{
    public static OneOf<TResult, TError> Bind<T, TResult, TError>(
        this OneOf<T, TError> oneOf,
        Func<T, OneOf<TResult, TError>> func)
    {
        return oneOf.Match<OneOf<TResult, TError>>(
            error => error,
            value => func(value)
        );
    }
}
using OneOf;
using System.Xml.Linq;

public class ValidationResult
{
    public ValidationResult(Exception ex) { /* handle exception */ }
    public ValidationResult() { /* success */ }
}

public OneOf<XDocument, Exception> ParseIntoXDoc(string xml)
{
    try
    {
        return XDocument.Parse(xml);
    }
    catch (Exception ex)
    {
        return ex;
    }
}

public OneOf<Version, Exception> ExtractVersion(XDocument doc)
{
    try
    {
        // Extract version logic
        return new Version("1.0.0");
    }
    catch (Exception ex)
    {
        return ex;
    }
}

public ValidationResult ValidateXML(Version ver, XDocument doc)
{
    // Validation logic
    return new ValidationResult();
}

public ValidationResult ValidateXML(string xml)
{
    return ParseIntoXDoc(xml)
        .Bind(doc => ExtractVersion(doc)
        .Bind(ver => ValidateXML(ver, doc)));
}

OneOf.Monads

Here you have an example using the Result Monad

 var result = GetLeague(new Query(leagueId, season))
                    .Bind(GetSeason)
                    .Bind(GetParticipants)
                    .Bind(GetWinner)
                    .Map(winner => winner.Name)
                    .DefaultWith(error => error.Reason);

CSharpFunctionalExtensions

Here you have an example of fluent monadic binding

 return _customerRepository.GetById(id)
    .ToResult("Customer with such Id is not found: " + id)
    .Ensure(customer => customer.CanBePromoted(), "The customer has the highest status possible")
    .Tap(customer => customer.Promote())
    .Tap(customer => _emailGateway.SendPromotionNotification(customer.PrimaryEmail, customer.Status))
    .Finally(result => result.IsSuccess ? Ok() : Error(result.Error));

LanguageExt

Here you have an example using the Result Monad

 var actual = await GetFolder("123")
    .Bind(folder => CreateFolder("New folder", folder.Id))
    .Bind(newFolder => UploadFile(stream, "New file name.txt", "text/text", newFolder))
    .Match(fileId => handleFileId(fileId), ex => handleException(ex));

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants