Skip to content

Support for C# pattern-matching

Compare
Choose a tag to compare
@louthy louthy released this 10 Dec 22:03

Language-ext was created before the C# pattern-matching feature existed. The default way to match within lang-ext is to use the Match(...) methods provided for most types.

There have been requests for the struct types to become reference-types so sub-types can represent the cases of types like Option<A>, Either<L, R>, etc. I don't think this is the best way forward for a number of reasons that I won't go in to here, but it would obviously be good to support the C# in-built pattern-matching.

So, now most types have a Case property, or in the case of delegate types like Try<A>, or in-built BCL types like Task<T>: a Case() extension method.

For example, this is how to match on an Option<int>:

var option = Some(123);

var result = option.Case switch
{
    SomeCase<int>(var x) => x,
    _                    => 0   // None
};

Next we can try matching on an Either<string, int>:

var either = Right<string, int>(123);

var result = either.Case switch
{
    RightCase<string, int>(var r) => r,
    LeftCase<string, int>(var _)  => 0,
    _                             => 0   // Bottom
};

This is where some of the issues of C#'s pattern-matching show up, they can get quite verbose compared to calling the Match method.

For async types you simply have to await the Case:

var either = RightAsync<string, int>(123);

var result = await either.Case switch
{
    RightCase<string, int>(var r) => r,
    LeftCase<string, int>(var _)  => 0,
    _                             => 0    // Bottom
};

The delegate types need to use Case() rather than Case:

var tryOption = TryOption<int>(123);

var result = tryOption.Case() switch
{
    SuccCase<int>(var r) => r,
    FailCase<int>(var _) => 0,
    _                    => 0   // None
};

All collection types support Case also, they all work with the same matching system and so the cases are always the same for all collection types:

static int Sum(Seq<int> seq) =>
    seq.Case switch
    {
        HeadCase<int>(var x)             => x,
        HeadTailCase<int>(var x, var xs) => x + Sum(xs),
        _                                => 0             // Empty
    };