Skip to content

PatternMatchingOtherTypesByType

David Arno edited this page Feb 17, 2020 · 3 revisions

Pattern Matching

Succinc<T> pattern matching guide: Other types, by type


[Note: this page needs work as the examples are poor]

Introduction

This pattern matching guide is split into the following sections:

Type-based pattern matching on general types is achieved via a single extension method, TypeMatch(), which must be followed by .To<ResultType>(). The reason for using To<T>() rather than just providing a TypeMatch that specifies the return type, is due to the way C# handles generics and methods. The rule is simple: either all type parameters can be inferred and so none need be specified, or all type parameters must be specified. As such, the following style would have to be used when, in this case, matching on an int and returning a string:

var result = 100.TypeMatch<int, string>()...

By using TypeMatch().To<ResultType>(), this becomes:

var result = 100.TypeMatch().To<string>()...

It's a subtle change, but in my opinion it aids readability as the type parameter is focused solely on the return type, rather than restating the value/reference type to be matched.

Note that an action-based version is not provided. This is because the new pattern matching features in C# 7 provide this ability already, so there seems little point in replicating it.

Syntax

The generalised syntax for patterns can be expressed using BNF-like syntax:

result = {item}.TypeMatch().To<{result type}>()
               [CaseOfExpression]...
               [ElseExpression]
               .Result();

CaseOfExpression ==>
    .Case<{some type}>()[WhereExpression].Do({value of some type} => {result type expression}) |
    .Case<{some type}>()[WhereExpression].Do({result type expression})

WhereExpression ==>
   .Where({item} => {boolean expression})

ElseExpression ==>
    .Else({item} => {result type expression}) |
    .Else({result type expression})

To explain the above syntax:

  • {} denotes a non-literal, eg {void expression} could be the empty expression, {}, or something like Console.WriteLine("hello").
  • Items in [] are optional.
  • | is or, ie [x|y] reads as "an optional x or y".
  • ... after [x] means 0 or more occurrences of x.
  • ==> is a sub-rule, which defines the expression on the left of this symbol.

Basic Usage

For the purposes of these examples, assume we have an interface, IAnimal, and calsses that implement it: Cat, Dog and Lizard.

public static bool IsAMammal(IAnmial animal) =>
    animal.TypeMatch().To<bool>()
          .CaseOf<Cat>().Do(x => true)
          .CaseOf<Dog>().Do(x => true)
          .CaseOf<Lizard>().Do(x => false)
          .Result();

Clearly, specifying all three animals is long-winded. We can simplify it by using Else:

public static bool IsAMammal(IAnmial animal) =>
    animal.TypeMatch().To<bool>()
          .CaseOf<Lizard>().Do(x => false)
          .Else(x => true)
          .Result();

One further change can be made. We are supplying a parameter, x to all the lambdas, which isn't then used. In this case, we can dispense with the lambdas and just specify the return values:

public static bool IsAMammal(IAnmial animal) =>
    animal.TypeMatch().To<bool>()
          .CaseOf<Lizard>().Do(false)
          .Else(true)
          .Result();

Obviously, the above could be simplified further just to:

public static bool IsAMammal(IAnmial animal) => animal is Lizard;

but the idea is to show a simple use of the feature, so hopefully you'll forgive me a possibly bad example of this pattern matching usage.

Matching Multiple Values

Let's redefine our types. IAnimal now has a boolean property, CanFly. So a new set of animal classes are created: Mammal, Bird, Reptile.

Using Where, we can then, for example, test if the animal is a bird, capable of flight:

public static bool IsAFlyingBird(IAnmial animal) =>
    animal.TypeMatch().To<bool>()
          .CaseOf<Bird>().Where(bird => bird.CanFly).Do(true)
          .Else(false)
          .Result();
Clone this wiki locally