-
Notifications
You must be signed in to change notification settings - Fork 15
PatternMatchingOptions
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 options - Covered here.
- Pattern matching on
Success<T>
- 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
An Option<T>
can contain some value of T
, or none
. Pattern matching on options therefore consists of matching values, via Some()
and matching nothing, via None()
.
The generalised syntax for option 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 = {option}.Match<{result type}>()
[SomeExpression | NoneExpression ]...
[ElseExpression]
.Result();
SomeExpression ==>
.Some()[WithExpression|WhereExpression].Do({value} => {result type expression}) |
.Some()[WithExpression|WhereExpression].Do({result type value})
NoneExpression ==>
.None().Do({result type value})
WithExpression ==>
.With({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}>
):
{option}.Match()
[SomeExpression | NoneExpression ]...
[ElseExpression]
.Exec();
SomeExpression ==>
.Some()[WithExpression|WhereExpression].Do({value} => {action on value})
NoneExpression ==>
.None().Do({parameterless action})
WithExpression ==>
.With({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 ContainsValue(Option<int> data)
=> data.Match()
.Some().Do(x => true)
.None().Do(x => false)
.Result();
public static void PrintOption(Option<int> data)
=> data.Match()
.Some().Do(Console.WriteLine)
.None().Do(() => { })
.Exec();
In ContainsValues
, we test against Some()
and None()
to return true/false accordingly. In the second case, we test against Some()
and None()
once more, and invoke an action depending on the option's state. If there's a value, we print it, otherwise nothing occurs (other than () => { }
being executed, which does nothing.
In both cases, we have used both Some()
and None()
, but we could optionally use Else()
:
public static bool ContainsValue(Option<int> data)
=> data.Match()
.Some().Do(x => true)
.Else(x => false)
.Result();
public static void PrintOption(Option<int> data)
=> data.Match()
.Some().Do(Console.WriteLine)
.Else(() => { })
.Exec();
Else()
or IgnoreElse()
is invoked if there is no match from any specified Some()
or None()
expressions.
One further change can be made to the functional example. We are supplying a parameter, x
, which isn't then used. In this case, we can dispense with the lambda and just specify the return value:
public static bool ContainsValue(Option<int> data)
=> data.Match()
.Some().Do(true)
.Else(false)
.Result();
The previous examples just matched an option with any value. We might want to match specific values though. We have two choices here: using Or()
and Where()
.
Firstly, using Or
we could write a method to print whether the value is 1, 2 or 3, and do nothing when it has no value:
public static void OneToThreeReporter(Option<int> data)
=> data.Match()
.Some().Of(1).Or(2).Or(3).Do(Console.WriteLine)
.Some().Do(i => Console.WriteLine("{0} isn't 1, 2 or 3!", i))
.None().Do(() => { })
.Exec();
If we want to check a range of values, we can use Where
:
public static string NumberNamer(Option<int> data)
{
var names = new[] {"One", "Two", "Three", "Four", "Five",
"Six", "Seven", "Eight", "Nine"};
return data.Match<string>()
.Some().Where(i => i >= 1 && i <= 9).Do(i => names[i-1])
.Some().Do(x => x.ToString())
.None().Do("None")
.Result();
}
So far, we have only considered distinct match patterns, ie where there is no overlap. In many cases, more than one Some()
pattern will be required and the match patterns may overlap. The following function highlights this:
public static string OddOrPositive(Option<int> value)
=> value.Match<string>()
.Some().Where(x => x % 2 == 1).Do(i => string.Format("{0} is odd", i))
.Some().Where(x => x > 0).Do(i => string.Format("{0} is positive", i))
.None().Do(_ => "No value")
.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(Option<int>.Some(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