Jack Roper

@zcabjro/either v0.5.0 is out!

@zcabjro/either is a small js library that allows users to represent values that can be one of two types. A common use-case of Either is as a functional alternative to throwing exceptions. For example, you could capture a possible error state within the type Either<Error, T>. I use it in my validation library @zcabjro/vivalidate.

type Either<A, B> =
  | Left<A, B>
  | Right<B, A>;

While still in initial development, this version is a marked improvement for two reasons (one big, one small):

  • First: the small reason. Because v0.4.0 re-implements Either as a union, we can more easily refine the type when inspecting the properties isLeft and isRight.
  • Second: the BIG reason. v0.4.0 and prior versions were not sound! For example, users could assign Either<Error, string> to Either<Error, number>. This was down to my attempts to implement a LeftProjection type similar to scala's. I haven't reached for it much in my projects so have decided to remove it in v0.5.0.

There is a small downside accompanying these changes, which is that the type-system no longer captures that mapping from Left will return a Left and similarly that mapping from Right will return a Right.

right('Alice')
  .map(s => s.length);
// returns Either<never, number>

Because of the new union implementation, when calling map, both cases of the union must have compatible signatures. I expect to continue occasionally grappling with the topic and hopefully I'll discover a solution (though given it is far more beneficial to have good refinement from Either to Left/Right, I'm happy with the trade-off).

Better refinement

Before, isLeft and isRight were methods that would refine the current instance of Either to either Left or Right. This works great, but isn't as helpful in practice as discriminating a union. Here is an example:

declare const x:
  Either<Error, string>;

// Before v0.4.0

if (x.isLeft()) {
  const l: Left<Error> = x;
} else {
  // ERROR!
  const r: Right<string> = x;
}

// After v0.4.0

if (x.isLeft) {
  const l: Left<Error> = x;
} else {
  // OK!
  const r: Right<string> = x;
}

This offers a lot more flexibility and saves us having to make redundant checks!

Smaller breaking changes

Taking inspiration from scala's Either, I originally tried to emulate it's case class interface by exposing functions Left and Right that would create instances of their respective types. In retrospect, this introduces confusion between the types and helper functions, so the interface has been re-worked. Either, Left and Right now refer to types while the functions left and right are available for creating instances.

LeftProjection has been removed as its unsound implementation was causing problems and I haven't found a huge need for projecting to Left so far in my projects. The goal was to support this functionality:

declare const either:
  Either<string[], number>;

either
  .left()
  .map(xs => xs.join(','));
// returns Either<string, number>

I may end up looking to re-introduce it though with the benefit of hindsight.