Skip to content
David Arno edited this page May 17, 2018 · 3 revisions

Union<T1, T2, T3>

SuccincT.Unions.Union<T1, T2, T3>

Provides a union of types T1, T2 and T3. Modelled on F# discriminated unions. Contains one of a T1 value, T2 value or a T3 value exclusively.


Construction

Instances of Union<T1, T2, T3> are either created via the overloaded constructor or via a union creator. Because unions can be directly created, T1, T2 and T3 must be of sufficiently different types for the compiler to be able to choose the correct constructor.

Creation via constructors

    public Union(T1 value)
    public Union(T2 value)
    public Union(T3 value)

Using constructors,union instances are created just like any other class:

var v1 = new Union<int, string, double>(1);
var v2 = new Union<int, string, double>("2");
var v3 = new Union<int, string, double>(1.0);

Creation via union creators

public static UnionCreator<T1, T2, T3> Creator()

Creates an instance of a union creator, which can be used thus:

var creator = Union<int, string, double>.Creator();
var v1 = creator.Create(1);
var v2 = creator.Create("2");
var v2 = creator.Create(3.0);

Equality

Whilst Union<T1, T2, T3> is a class, not a struct, it overrides both Equals and the == & != operator pair to provide value-based equality, as demonstrated by the following code:

var creator = Union<int, string, double>.Creator();
var a = creator.Create(1);
var b = creator.Create(1);
var c = creator.Create(2);
var d = creator.Create("1");
var e = creator.Create("1");
var f = creator.Create(1.0);

// The following expressions are all true
a == a
a == b
a != c
b != d
d == e
a.Equals(b)
d.Equals(e)
f != a   // int and double are different types, so the values aren't compared.

Functional Use

Union<T1, T2, T3> uses Succinc<T>'s pattern matching capabilities to perform an action or generate a result according to its value. Two versions of the match method are directly supported by Union<T1, T2, T3>:

Match()

    public OptionMatcher<T1, T2, T3> Match()

Match() supports the construction of patterns resulting in actions (void methods) to invoke upon a sucessful match. The pattern definition must be terminated with Exec().

The format of an option match pattern is as follows:

option.Match()
      .Case1()|CaseOf<T1>()<optional guard>.Do(value => action on value)
      .Case2()|CaseOf<T2>()<optional guard>.Do(value => action on value)
      .Case3()|CaseOf<T3>()<optional guard>.Do(value => action on value)
      [.Else(union => action on no match) |
       .IgnoreElse()]
      .Exec();

Case1(), Case2() and Case3() can take an optional guard of two forms:

.CaseN().Of(value1).Or(value2).Or(value3)...Do(value => action on value)
.CaseN().Where(value => boolean expression).Do(value => action on value)

CaseOf<type>() can likewise take those optional guards:

.CaseOf<type>().Of(value1).Or(value2).Or(value3)...Do(value => action on value)
.CaseOf<type>().Where(value => boolean expression).Do(value => action on value)

Multiple Case1(), Case2(), Case3() and CaseOf<type>() expressions may be defined. Each is compared in turn against that type's value (assuming there is one) until a match is found. The action is then invoked and no further matching occurs.

The Else action is used if no match was found. The union itself is passed as a parameter to the associated action. If no action is required when no match occurs, IgnoreElse can be used instead.

If no match is found, and no Else() or IgnoreElse is defined, a SuccincT.PatternMatchers.NoMatchException will be thrown

If CaseOf<type> is used, with type being different to both T1 and T2, a SuccincT.PatternMatchers.InvalidCaseOfTypeException will be thrown

Match<TResult>()

    public OptionMatcher<T1, T2, T3, TResult> Match<T>()

Match<T>() supports the construction of patterns resulting in functions returning a TResult to invoke upon match. The pattern must be terminated with Result().

The format of an option match pattern is as follows:

option.Match<T>()
      .Case1()|CaseOf<T1>()<optional guard>.Do(value => func resulting in a T)
      .Case2()|CaseOf<T2>()<optional guard>.Do(value => func resulting in a T)
      .Case3()|CaseOf<T3>()<optional guard>.Do(value => func resulting in a T)
      .Else(union => no match func resulting in a T)
      .Result()

Case1(), Case2() and Case3() can take an optional guard of two forms:

.CaseN().Of(value1).Or(value2).Or(value3)...Do(value => func resulting in a T)
.CaseN().Where(value => boolean expression).Do(value => func resulting in a T)

CaseOf<type>() can likewise take those optional guards:

.CaseOf<type>().Of(value1).Or(value2).Or(value3)...Do(value => func resulting in a T)
.CaseOf<type>().Where(value => boolean expression).Do(value => func resulting in a T)

Multiple Case1(), Case2(), Case3() and CaseOf<type>() expressions may be defined. Each is compared in turn again that type's value (assume there is one) until a match is found. The function is then invoked and no further matching occurs.

The Else function is used if no match was found. The union itself is passed as a parameter to the associated function.

If no match is found, and no Else() defined, a SuccincT.PatternMatchers.NoMatchException will be thrown

If CaseOf<type> is used, with type being different to both T1 and T2, a SuccincT.PatternMatchers.InvalidCaseOfTypeException will be thrown

For further details see the pattern matching options guide.

Imperative Use

As an alternative to using an union in a functional style, its value can be directly accessed by more traditional, imperative style C# code. Three read-only properties are provided for this purpose:

Value

public TResult Value<TResult>()

Returns the value of the union directly. If the value of the union is not of the TResult type, an InvalidOperationException will be thrown.

Case

public Variant Case { get; }

Returns Variant.Case1 if the value is of T1; Variant.Case2 if the value is of T2; or Variant.Case3 if the value is of T3.

Case1

public T1 Case1 { get; }

If Case is Variant.Case1, this will return the value held by the union. Otherwise an InvalidOperationException will be thrown.

Case2

public T2 Case2 { get; }

If Case is Variant.Case2, this will return the value held by the union. Otherwise an InvalidOperationException will be thrown.

Case3

public T3 Case3 { get; }

If Case is Variant.Case3, this will return the value held by the union. Otherwise an InvalidOperationException will be thrown.

HasValueOf<T>

public bool HasValueOf<T>()

For the current case, Case1, Case2 or Case3, if that case's type matches T then the result is true; otherwose it's false. For example,

var union = new Union<int, string, Plants>(2);
var hasInteger = union.HasValueOf<int>(); // hasInteger will be true
Clone this wiki locally