Skip to content

Pattern matching

Leonid Gordo edited this page Aug 5, 2016 · 15 revisions

The one of the fundamental thing in functional way programming is the pattern matching. Unfortunately, the C# does not provide a native support of this concept. To fill this gap the Functional.Fluent library offers the implementation of this feature including type pattern matching and list pattern matching.

Value pattern matching

The most obvious way to imitate the matching is to use ApplyIf bunch of methods. Look at the example below:

var s = "fox";
var r = s.ToMaybe()
   .ApplyIf(x => x == "bird", x => x + " can fly")
   .ApplyIf(x => x == "fox", x => x + " can run");

This is the very first approach that works, but the library gives you the dedicated type for matching: Matcher. The use is very straightforward:

var m = new Matcher<int, string>
{
    {x => x % 2 == 0, x => x.ToString() + " is even"},
    {x => true, x => x.ToString() + " is odd"}  
};
var r = m.Match(4);

If you want to match just against the exact values then review a simplified form:

var m = new Matcher<int, string>
{
    {5, x => x.ToString() + " is five"},
    {4, x => x.ToString() + " is four"},
    {3, x => x.ToString() + " is three"},
    {x => true, x => x.ToString() + " is something other"}  
};
var r = m.Match(4);

The case can accept not only the single value but the collection of values:

var m = new Matcher<int, string>
{
    {new[] {2, 4, 6}, x => x.ToString() + " is two or four or six"},
    {new[] {3, 5, 7}, x => x.ToString() + " is three or five or seven"},
    {x => true, x => x.ToString() + " is something other"}  
};
var r = m.Match(i);

Fluent syntax

The library follow the concept of fluent code style, so it tries to provide the ability to write code in fluent way for every bit of it. The same is true for pattern matching feature.

result = "fox".ToMaybe().Match()
            .With(x => x == "bird", x => x + " can fly")
            .With(x => x == "fox", x => x + " can run")
            .Else(x => x + " can do something else")
            .Do();

The same works for exact values:

result = "fox".ToMaybe().Match()
            .With("bird", x => x + " can fly")
            .With("fox", x => x + " can run")
            .Else(x => x + " can do something else")
            .Do();

The matcher can be converted to Func<> to be called later on.

 var m = new Matcher<string, string>
 {
     {x => x == "bird", x => x + " can fly"},
     {x => x == "fox", x => x + " can run"},
     {x => true, x => x + " can do something else"},
  }.ToFunc();

 ...

 m("bird");   // here is matching actually runs

Type pattern matching

To match the given type against the set of types use the TypeMatcher.

var m = new TypeMatcher<string>
{
    { Case.Is<string>(), s => s},
    { Case.Is<StringBuilder>(), s => s.ToString()}
};

string test1 = "a simple string";
m.Match(test1); // returns "a simple string"

Or using fluent syntax:

result = ((object) "string").ToMaybe().TypeMatch()
            .With(Case.Is<string>(), s => s)
            .With(Case.Is<StringBuilder>(), s => s.ToString())
            .Else(_ => "that's an object")
            .Do();

The type matching can be enriched with predicates. The predicate allows to add additional test for the case:

result = ((object)"the long string").ToMaybe().TypeMatch()
            .With(Case.Is<string>(), s => s.Length > 10, s => s + "!")
            .With(Case.Is<string>(), s => s)
            .With(Case.Is<StringBuilder>(), s => s.ToString())
            .Else(_ => "that's an object")
            .Do();
Clone this wiki locally