Either Type

functional csharp

Introduction

Sometimes we may need to return two separate types of values from a function, one type if the function executed successfully and one type if the function encountered errors - validation or exception.

The Either type can help with this.

The Either<Left,Right> will return the Left type for errors and the Right type in case of no errors.

Something like this: Either<L,R> = Left(L) | Right R

  • Left(L) - It wraps a value of type L, capturing details about the error.

  • Right R - It wraps a value of type R, capturing the success value.

The Left Type

Define the Left type.

public static class Either
{
  public struct Left<L>
  {
	 internal L Value { get; }
	 internal Left(L value) { Value = value; }

	 public override string ToString() => $"Left({Value})";
  }
}

A new Left value can be created:-

var left = new Either.Left<string>("test");
Assert.Equal("test", left.Value);

The Right Type

Define the Right type in the same static class Either:

public struct Right<R>
{
	internal R Value { get; }

	internal Right(R value) { Value = value; }

	public override string ToString() => $"Right({Value})";
}

A new Right value can be created:-

var right = new Either.Right<string>("test");
Assert.Equal("test", right.Value);

The Either <L,R> Type

Now that we have the Left and Right type, we can move start defining the Either<L,R>.

public struct Either<L, R>
{
    internal L Left { get; }

    internal R Right { get; }

    private bool IsRight { get; }

    private bool IsLeft => !IsRight;

    internal Either(L left)
    {
        IsRight = false;
        Left = left;
        Right = default(R);
    }

    internal Either(R right)
    {
        IsRight = true;
        Right = right;
        Left = default(L);
    }
}

Testing the left value:

Either<int, string> leftValue = new Either<int, string>(1); (1)
Assert.Equal(1, leftValue.Left); (2)
Assert.Null(leftValue.Right); (3)
  1. The Either<L,R> type has 2 constructors:- one for the L type and one for the R type. L is of int type, so the constructor with L is called.

  2. The Left value gets set to the int value passed which is 1.

  3. The Right value gets set to the default® which in this case is a string and the default value of string is null.

Testing the right value:

Either<int, string> rightValue = new Either<int, string>("test"); (1)
Assert.Equal(0, rightValue.Left); (2)
Assert.Equal("test", rightValue.Right); (3)
  1. The Either<L,R> type has 2 constructors:- one for the L type and one for the R type. L in this case is int, so the constructor with R is called.

  2. The Left value gets set to he default(L) value which in this case is an int and the default value of int is 0.

  3. The Right value gets set to the string value passed which is "test".

The Implicit Operators

Define the implicit operators that make it convenient to convert an L or R type to an Either<L,R> type.

public static implicit operator Either<L, R>(L left) => new(left);
public static implicit operator Either<L, R>(R right) => new(right);

public static implicit operator Either<L, R>(Either.Left<L> left) => new(left.Value);
public static implicit operator Either<L, R>(Either.Right<R> right) => new(right.Value);

Testing the implicit operators:

Left value tests:

Either<int, string> leftValue1 = 1; (1)
Either<int, string> leftValue2 = new Either.Left<int>(1); (2)

Assert.Equal(1, leftValue1.Left);
Assert.Null(leftValue1.Right);

Assert.Equal(1, leftValue2.Left);
Assert.Null(leftValue2.Right);
  1. Converts the int value to Either<int, string>.

  2. Converts the Either.Left<int> to Either<int,string> value.

Right value tests:

Either<int, string> rightValue1 = "test"; (1)
Either<int, string> rightValue2 = new Either.Right<string>("test"); (2)

Assert.Equal("test", rightValue1.Right);
Assert.Equal(0, rightValue1.Left);

Assert.Equal("test", rightValue2.Right);
Assert.Equal(0, rightValue2.Left);
  1. Converts the string value to Either<int,string>.

  2. Converts the Either.Right<string> to Either<int,string> value.

The F static class

It is a bit annoying to type new Either.Right<string>("test") and new Either.Left<int>(1) to create a Left and Right type.

It is better to type Left(1) and Right("test") to create the Either<L,R> type.

The F static class can help.

public static partial class F
{
    public static Either.Left<L> Left<L>(L l) => new(l);
    public static Either.Right<R> Right<R>(R r) => new(r);
}

Now by importing the static class, we can use these functions as follows:

using static F; (1)

var left = Left<int>(0); (2)
var right = Right<string>("test"); (3)

Assert.Equal(0, left.Value);
Assert.Equal("test", right.Value);
  1. The using declaration.

  2. Creates an Either.Left<int> type.

  3. Creates an Either.Right<string> type.

The Match method

This is the all important Match method of Either<L,R>.

We will use this function to read the Left and Right values of the Either<L,R> type.

There is no other way the caller can read the Left and Right values but through the Match method.

This forces the caller to handle both the cases.

Lets add the method to the Either<L,R> type.

public TR Match<TR>(Func<L, TR> left, Func<R, TR> right)
       => IsLeft ? left(this.Left) : right(this.Right);

Testing the method when the Left value is passed:

Either<int, string> leftValue = Left<int>(1);

var returnValue = leftValue.Match(
	left: l => $"Value is {l}",
	right: r => $"Value is {r}"
);

Assert.Equal("Value is 1", returnValue);

Testing the method when the Right value is passed:

Either<int, string> rightValue = Right<string>("test");

var returnValue = rightValue.Match(
	left: l => $"Value is {l}",
	right: r => $"Value is {r}"
);

Assert.Equal("Value is test", returnValue);

The Map/Select function

Now suppose we would like to transform the Left or Right value of Either<L,R> by passing it to a transform function T → R

Here is an example:

Either<int, string> testMap = Right<string>("test"); (1)

var count = testMap
				.Map(s => s.Length) (2)
				.Match(
					left: l => l,
					right: r => r
				);

        Assert.Equal(4, count); (3)
  1. Define a Right value as a string.

  2. s ⇒ s.Length is the transformation function that converts the string to int.

  3. Assert the value is 4.

Note that we transform only the Right value and not the left one.

This is done because we want to treat the`Left` value as the value containing the error data.

We do not want to do any transformation on the error data but just pass it along straight to the Match function at the end.

How do we write such a function:-

public static class EitherExtensions (1)
{
    public static Either<L, RR> Map<L, R, RR>(this Either<L, R> @this, Func<R, RR> f)
    => @this.Match<Either<L, RR>>(
            l => F.Left(l),
            r => F.Right(f(r)) (2)
        );
}
  1. Create the EitherExtensions class.

  2. Execute the function only for the Right value.

In LINQ, the word used for Map is Select.

Select can be defined in terms of Map as follows:

public static Either<L, R> Select<L, T, R>(this Either<L, T> @this, Func<T, R> map)
	=> @this.Map(map);

Example rewritten using Select:

Either<int, string> testMap = Right<string>("test");

var count = testMap
	        .Select(s => s.Length)
                .Match(
                    left: l => l,
                    right: r => r
                );

Assert.Equal(4, count);

The Bind/SelectMany function

Now suppose we have like to transform the Left or Right value of Either<L,R> by passing it to a transform function that takes a normal value and returns an Either. T → Either<L,R>

Here is an example:

Either<string, string> testMap = Right<string>("test");

var count = testMap
				.Bind(s => new Either<string, int>(s.Length))
				.Match(
					left: l => 0,
					right: r => r
				);

Assert.Equal(4, count);
  1. Define a Right value as a string.

  2. s ⇒ new Either<string, int>(s.Length) is the transformation function that converts the string to an Either<string,int>.

  3. Assert the value is 4.

Again, we do not want to process the Left value, just the Right.

Here is the bind function:

public static Either<L, RR> Bind<L, R, RR>(this Either<L, R> @this, Func<R, Either<L, RR>> f)
    => @this.Match(
        l => F.Left(l),
        r => f(r)
    );

In LINQ, the word used for Bind is SelectMany.

SelectMany can be defined in terms of Bind as follows:

public static Either<L, RR> SelectMany<L, R, RR>(this Either<L, R> @this, Func<R, Either<L, RR>> bind)
    => @this.Bind(bind);

Example rewritten using SelectMany:

Either<string, string> testMap = Right<string>("test");

var count = testMap
            .Bind(s => new Either<string, int>(s.Length))
            .Match(
                left: l => 0,
                right: r => r
            );

Assert.Equal(4, count);

References