-
Notifications
You must be signed in to change notification settings - Fork 1
Pattern matching
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.
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);
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
The hello world for pattern matching is the factorial calculation. Let's review this example. We define factorial
function:
int factorial(int n) => n.ToMaybe().Match()
.With(0, 1, _ => 1)
.Else(v => v* factorial(v - 1))
.Do();
Now we can call this function:
result = factorial(5) // will return 120
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();
The library makes matching of the list elements as simple as matching of the scalar values.
var list = new [] { "good morning", "good afternoon", "good evening" };
var result = list.ToMaybe().Match()
.With((h, t) => Tuple.Create(h.ToUpper(), t))
.Do();
// result is Tuple("GOOD MORNING", 2)
To check if the list is empty you can use Empty
case:
var list = new List<string>();
var result = list.ToMaybe().Match()
.With((h, t) => h.ToUpper())
.Empty(() => "List is empty")
.Do();
Let's review the example of the function that sums the even number in the list:
int sum_even(IEnumerable<int> value) => value.ToMaybe().Match()
.With(v => v % 2 == 0, (head, tail) => head + sum_even(tail))
.With((_, tail) => sum_even(tail))
.Empty(() => 0)
.Do();