# Either bifunctor by Mark Seemann

*Either forms a bifunctor. An article for object-oriented programmers.*

This article is an instalment in an article series about bifunctors. As the overview article explains, essentially there's two practically useful bifunctors: pairs and Either. In the previous article, you saw how a pair (a two-tuple) forms a bifunctor. In this article, you'll see how Either also forms a bifunctor.

### Mapping both dimensions #

In the previous article, you saw how, if you have maps over both dimensions, you can trivially implement `SelectBoth`

(what Haskell calls `bimap`

):

`return source.SelectFirst(selector1).SelectSecond(selector2);`

The relationship can, however, go both ways. If you implement `SelectBoth`

, you can derive `SelectFirst`

and `SelectSecond`

from it. In this article, you'll see how to do that for Either.

Given the Church-encoded Either, the implementation of `SelectBoth`

can be achieved in a single expression:

public static IEither<L1, R1> SelectBoth<L, L1, R, R1>( this IEither<L, R> source, Func<L, L1> selectLeft, Func<R, R1> selectRight) { return source.Match<IEither<L1, R1>>( onLeft: l => new Left<L1, R1>( selectLeft(l)), onRight: r => new Right<L1, R1>(selectRight(r))); }

Given that the input `source`

is an `IEither<L, R>`

object, there's isn't much you can do. That interface only defines a single member, `Match`

, so that's the only method you can call. When you do that, you have to supply the two arguments `onLeft`

and `onRight`

.

The `Match`

method is defined like this:

T Match<T>(Func<L, T> onLeft, Func<R, T> onRight)

Given the desired return type of `SelectBoth`

, you know that `T`

should be `IEither<L1, R1>`

. This means, then, that for `onLeft`

, you must supply a function of the type `Func<L, IEither<L1, R1>>`

. Since a functor is a structure-preserving map, you should translate a *left* case to a *left* case, and a *right* case to a *right* case. This implies that the concrete return type that matches `IEither<L1, R1>`

for the `onLeft`

argument is `Left<L1, R1>`

.

When you write the function with the type `Func<L, IEither<L1, R1>>`

as a lambda expression, the input argument `l`

has the type `L`

. In order to create a `new Left<L1, R1>`

, however, you need an `L1`

object. How do you produce an `L1`

object from an `L`

object? You call `selectLeft`

with `l`

, because `selectLeft`

is a function of the type `Func<L, L1>`

.

You can apply the same line of reasoning to the `onRight`

argument. Write a lambda expression that takes an `R`

object `r`

as input, call `selectRight`

to turn that into an `R1`

object, and return it wrapped in a `new Right<L1, R1>`

object.

This works as expected:

> new Left<string, int>("foo").SelectBoth(string.IsNullOrWhiteSpace, i => new DateTime(i)) Left<bool, DateTime>(false) > new Right<string, int>(1337).SelectBoth(string.IsNullOrWhiteSpace, i => new DateTime(i)) Right<bool, DateTime>([01.01.0001 00:00:00])

Notice that both of the above statements evaluated in *C# Interactive* use the same projections as input to `SelectBoth`

. Clearly, though, because the inputs are first a `Left`

value, and secondly a `Right`

value, the outputs differ.

### Mapping the left side #

When you have `SelectBoth`

, you can trivially implement the translations for each dimension in isolation. In the previous article, I called these methods `SelectFirst`

and `SelectSecond`

. In this article, I've chosen to instead name them `SelectLeft`

and `SelectRight`

, but they still corresponds to Haskell's `first`

and `second`

`Bifunctor`

functions.

public static IEither<L1, R> SelectLeft<L, L1, R>(this IEither<L, R> source, Func<L, L1> selector) { return source.SelectBoth(selector, r => r); }

The method body is literally a one-liner. Just call `SelectBoth`

with `selector`

as the projection for the left side, and the identity function as the projection for the right side. This ensures that if the actual value is a `Right<L, R>`

object, nothing's going to happen. Only if the input is a `Left<L, R>`

object will the projection run:

> new Left<string, int>("").SelectLeft(string.IsNullOrWhiteSpace) Left<bool, int>(true) > new Left<string, int>("bar").SelectLeft(string.IsNullOrWhiteSpace) Left<bool, int>(false) > new Right<string, int>(42).SelectLeft(string.IsNullOrWhiteSpace) Right<bool, int>(42)

In the above *C# Interactive* session, you can see how projecting three different objects using `string.IsNullOrWhiteSpace`

works. When the `Left`

object indeed does contain an empty string, the result is a `Left`

value containing `true`

. When the object contains `"bar"`

, however, it contains `false`

. Furthermore, when the object is a `Right`

value, the mapping has no effect.

### Mapping the right side #

Similar to `SelectLeft`

, you can also trivially implement `SelectRight`

:

public static IEither<L, R1> SelectRight<L, R, R1>(this IEither<L, R> source, Func<R, R1> selector) { return source.SelectBoth(l => l, selector); }

This is another one-liner calling `SelectBoth`

, with the difference that the identity function `l => l`

is passed as the first argument, instead of as the last. This ensures that only `Right`

values are mapped:

> new Left<string, int>("baz").SelectRight(i => new DateTime(i)) Left<string, DateTime>("baz") > new Right<string, int>(1_234_567_890).SelectRight(i => new DateTime(i)) Right<string, DateTime>([01.01.0001 00:02:03])

In the above examples, `Right`

integers are projected into `DateTime`

values, whereas `Left`

strings stay strings.

### Identity laws #

Either obeys all the bifunctor laws. While it's formal work to prove that this is the case, you can get an intuition for it via examples. Often, I use a property-based testing library like FsCheck or Hedgehog to demonstrate (not prove) that laws hold, but in this article, I'll keep it simple and only cover each law with a parametrised test.

private static T Id<T>(T x) => x; public static IEnumerable<object[]> BifunctorLawsData { get { yield return new[] { new Left<string, int>("foo") }; yield return new[] { new Left<string, int>("bar") }; yield return new[] { new Left<string, int>("baz") }; yield return new[] { new Right<string, int>( 42) }; yield return new[] { new Right<string, int>( 1337) }; yield return new[] { new Right<string, int>( 0) }; } } [Theory, MemberData(nameof(BifunctorLawsData))] public void SelectLeftObeysFirstFunctorLaw(IEither<string, int> e) { Assert.Equal(e, e.SelectLeft(Id)); }

This test uses xUnit.net's `[Theory]`

feature to supply a small set of example input values. The input values are defined by the `BifunctorLawsData`

property, since I'll reuse the same values for all the bifunctor law demonstration tests.

The tests also use the identity function implemented as a `private`

function called `Id`

, since C# doesn't come equipped with such a function in the Base Class Library.

For all the `IEither<string, int>`

objects `e`

, the test simply verifies that the original Either `e`

is equal to the Either projected over the first axis with the `Id`

function.

Likewise, the first functor law applies when translating over the second dimension:

[Theory, MemberData(nameof(BifunctorLawsData))] public void SelectRightObeysFirstFunctorLaw(IEither<string, int> e) { Assert.Equal(e, e.SelectRight(Id)); }

This is the same test as the previous test, with the only exception that it calls `SelectRight`

instead of `SelectLeft`

.

Both `SelectLeft`

and `SelectRight`

are implemented by `SelectBoth`

, so the real test is whether this method obeys the identity law:

[Theory, MemberData(nameof(BifunctorLawsData))] public void SelectBothObeysIdentityLaw(IEither<string, int> e) { Assert.Equal(e, e.SelectBoth(Id, Id)); }

Projecting over both dimensions with the identity function does, indeed, return an object equal to the input object.

### Consistency law #

In general, it shouldn't matter whether you map with `SelectBoth`

or a combination of `SelectLeft`

and `SelectRight`

:

[Theory, MemberData(nameof(BifunctorLawsData))] public void ConsistencyLawHolds(IEither<string, int> e) { bool f(string s) => string.IsNullOrWhiteSpace(s); DateTime g(int i) => new DateTime(i); Assert.Equal(e.SelectBoth(f, g), e.SelectRight(g).SelectLeft(f)); Assert.Equal( e.SelectLeft(f).SelectRight(g), e.SelectRight(g).SelectLeft(f)); }

This example creates two local functions `f`

and `g`

. The first function, `f`

, just delegates to `string.IsNullOrWhiteSpace`

, although I want to stress that this is just an example. The law should hold for any two (pure) functions. The second function, `g`

, creates a new `DateTime`

object from an integer, using one of the `DateTime`

constructor overloads.

The test then verifies that you get the same result from calling `SelectBoth`

as when you call `SelectLeft`

followed by `SelectRight`

, or the other way around.

### Composition laws #

The composition laws insist that you can compose functions, or translations, and that again, the choice to do one or the other doesn't matter. Along each of the axes, it's just the second functor law applied. This parametrised test demonstrates that the law holds for `SelectLeft`

:

[Theory, MemberData(nameof(BifunctorLawsData))] public void SecondFunctorLawHoldsForSelectLeft(IEither<string, int> e) { bool f(int x) => x % 2 == 0; int g(string s) => s.Length; Assert.Equal(e.SelectLeft(x => f(g(x))), e.SelectLeft(g).SelectLeft(f)); }

Here, `f`

is the *even* function, whereas `g`

is a local function that returns the length of a string. The second functor law states that mapping `f(g(x))`

in a single step is equivalent to first mapping over `g`

and then map the result of that using `f`

.

The same law applies if you fix the first dimension and translate over the second:

[Theory, MemberData(nameof(BifunctorLawsData))] public void SecondFunctorLawHoldsForSelectRight(IEither<string, int> e) { char f(bool b) => b ? 'T' : 'F'; bool g(int i) => i % 2 == 0; Assert.Equal(e.SelectRight(x => f(g(x))), e.SelectRight(g).SelectRight(f)); }

Here, `f`

is a local function that returns `'T'`

for `true`

and `'F'`

for `false`

, and `g`

is a local function that, as you've seen before, determines whether a number is even. Again, the test demonstrates that the output is the same whether you map over an intermediary step, or whether you map using only a single step.

This generalises to the composition law for `SelectBoth`

:

[Theory, MemberData(nameof(BifunctorLawsData))] public void SelectBothCompositionLawHolds(IEither<string, int> e) { bool f(int x) => x % 2 == 0; int g(string s) => s.Length; char h(bool b) => b ? 'T' : 'F'; bool i(int x) => x % 2 == 0; Assert.Equal( e.SelectBoth(x => f(g(x)), y => h(i(y))), e.SelectBoth(g, i).SelectBoth(f, h)); }

Again, whether you translate in one or two steps shouldn't affect the outcome.

As all of these tests demonstrate, the bifunctor laws hold for Either. The tests only showcase six examples for either a string or an integer, but I hope it gives you an intuition how any Either object is a bifunctor. After all, the `SelectLeft`

, `SelectRight`

, and `SelectBoth`

methods are all generic, and they behave the same for all generic type arguments.

### Summary #

Either objects are bifunctors. You can translate the first and second dimension of an Either object independently of each other, and the bifunctor laws hold for any pure translation, no matter how you compose the projections.

As always, there can be performance differences between the various compositions, but the outputs will be the same regardless of composition.

A functor, and by extension, a bifunctor, is a structure-preserving map. This means that any projection preserves the structure of the underlying container. For Either objects, it means that *left* objects remain *left* objects, and *right* objects remain *right* objects, even if the contained values change. Either is characterised by containing exactly one value, but it can be either a *left* value or a *right* value. No matter how you translate it, it still contains only a single value - *left* or *right*.

The other common bifunctor, *pair*, is complementary. Not only does it also have two dimensions, but a pair always contains both values at once.