Skip to content

PatternMatchingSuccess

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

Pattern Matching

Succinc<T> pattern matching guide: Success<T>


Introduction

This pattern matching guide is split into the following sections:

A Success<T> can contain some error of T, or true (success). Pattern matching on successes therefore consists of matching error values, via Error() and matching success, via Success().

Syntax

The generalised syntax for success patterns can be expressed using BNF-like syntax. As with all Succinc<T> pattern matching cases, there are two types of match. Firstly, matching and returning a value:

result = {success}.Match<{result type}>()
    [ErrorExpression|SuccessExpression ]...
    [ElseExpression]
    .Result();

ErrorExpression ==>
    .Error()[OfExpression|WhereExpression].Do({value} => {result type expression}) |
    .Error()[OfExpression|WhereExpression].Do({result type value}) |

SuccessExpression ==>
    .Success().Do({result type value})

OfExpression ==> 
    .Of({value})[.Or({value})]...

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

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

And the alternative is a match that invokes a statement (ie, an Action<{item type}>):

{success}.Match()
    [ErrorExpression | SuccessExpression ]...
    [ElseExpression]
    .Exec();

ErrorExpression ==>
    .Error()[OfExpression|WhereExpression].Do({value} => {action on value})

SuccessExpression ==>
    .Success().Do({parameterless action})

OfExpression ==>
    .Of({value})[.Or({value})]...

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

ElseExpression ==>
    .Else({option} => {action on option}) |
    .IgnoreElse()

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

The most basic form is matching on there being a value or not:

public static bool ContainsError(Success<int> data)
    => data.Match()
           .Error().Do(_ => true)
           .Success().Do(() => false)
           .Result();

public static void PrintSuccess(Success<int> data)
    => data.Match()
           .Error().Do(Console.WriteLine)
           .Success().Do(() => {})
           .Exec();

In ContainsError, we test against Error() and Success() to return true/false accordingly. In PrintSuccess, we test against Error() and Success() once more, and invoke an action depending on the success' state. If there's an error, we print it, otherwise nothing occurs (other than () => { } being executed, which does nothing.

In both cases, we have used both Error() and Success(), but we could optionally use Else():

public static bool ContainsError(Success<int> data)
    =>  data.Match()
            .Error().Do(_ => true)
            .Else(_ => false)
            .Result();

public static void PrintSuccess(Success<int> data)
    => data.Match()
           .Error().Do(Console.WriteLine)
           .Else(() => {})
           .Exec();

Else() or IgnoreElse() is invoked if there is no match from any specified Error() or Success() expressions.

One further change can be made to the functional example. We are supplying a parameter, _, which isn't then used. In this case, we can dispense with the lambda and just specify the return value:

public static bool ContainsError(Success<int> data)
    => data.Match()
           .Error().Do(true)
           .Else(false)
           .Result();

Matching Individual Values

The previous examples just matched a Success<T> with any value. We might want to match specific errors though. We have two choices here: using Of() and Where().

Firstly, using Of we could write a method to print whether the error is 1, 2 or 3, and do nothing when it is a success:

public static void OneToThreeReporter(Success<int> data)
    => data.Match()
           .Error().Of(1).Or(2).Or(3).Do(Console.WriteLine)
           .Error().Do(i => Console.WriteLine($"{i} isn't 1, 2 or 3!"))
           .Success().Do(() => {})
           .Exec();

If we want to check a range of values, we can use Where:

public static string ErrorNumberNamer(Success<int> data)
{
    var names = new[] {"Fatal", "Error", "Warning", "Info", "Debug"};

    return data.Match<string>()
               .Error().Where(i => i >= 1 && i <= 4).Do(i => names[i-1])
               .Error().Do(x => $"Unknown error: {x}")
               .Success().Do("Success")
               .Result();
}

Match Order

So far, we have only considered distinct match patterns, ie where there is no overlap. In many cases, more than one Error() pattern will be required and the match patterns may overlap. The following function highlights this:

public static string OddOrPositive(Success<int> value)
    => value.Match<string>()
            .Error().Where(x => x % 2 == 1).Do(i => string.Format("{0} is odd", i))
            .Error().Where(x => x > 0).Do(i => string.Format("{0} is positive", i))
            .Success().Do(_ => "No error")
            .Else(i => string.Format("{0} is neither odd, nor positive"))
            .Result();
}

Clearly in this situation, all positive odd integers will match both Where clauses. The matching mechanism tries each match in the order specified and stops on the first match. So OddOrPositive(Success.CreateFailure(1)) will return 1 is odd, rather than 1 is positive.

Clone this wiki locally