-
Notifications
You must be signed in to change notification settings - Fork 15
PatternMatchingSuccess
This pattern matching guide is split into the following sections:
- Pattern matching on collections.
- Pattern matching on discriminated unions.
-
Pattern matching on
Either<TLeft, TRight>
. - Pattern matching on
Option<T>
- Pattern matching on
Success<T>
- Covered here. - Pattern matching on tuples
- Pattern matching on
ValueOrError
andValueOrError<TValue, TError>
- Type-based pattern matching for all other types
- Value-based pattern matching on all other types
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()
.
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 likeConsole.WriteLine("hello")
. - Items in
[]
are optional. -
|
isor
, 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.
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();
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();
}
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
.
Action
/Func
conversionsCycle
methods- Converting between
Action
andFunc
- Extension methods for existing types that use
Option<T>
- Indexed enumerations
IEnumerable<T>
cons- Option-based parsers
- Partial function applications
- Pattern matching
- Pipe Operators
- Typed lambdas
Any
Either<TLeft,TRight>
None
Option<T>
Success<T>
Union<T1,T2>
Union<T1,T2,T3>
Union<T1,T2,T3,T4>
Unit
ValueOrError