Progressions of concision

January 30, 2016

Either

Let's implement a simple sum type, Either, in a few polymorphic languages.

Java

class NoValueException extends Exception { }

interface Either<A,B> {

  boolean isLeft();
  boolean isRight();

  public A leftValue() throws NoValueException;
  public B rightValue() throws NoValueException;

}

class Left<A,B> implements Either<A,B> {

  public final A a;

  public Left(A a) {
    this.a = a;
  }

  @Override
  public boolean isLeft() {
    return true;
  }

  @Override
  public boolean isRight() {
    return false;
  }

  @Override
  public A leftValue() throws NoValueException {
    return a;
  }

  @Override
  public B rightValue() throws NoValueException {
    throw new NoValueException();
  }

  @Override
  public boolean equals(Object x) {
    return toString().equals(x.toString());
  }

  @Override
  public int hashCode() {
    return toString().hashCode();
  }

  @Override
  public String toString() {
    return "Left(" + a.toString() + ")";
  }

}

class Right<A,B> implements Either<A,B> {

  public final B b;

  public Right(B b) {
    this.b = b;
  }

  @Override
  public boolean isLeft() {
    return false;
  }

  @Override
  public boolean isRight() {
    return true;
  }

  @Override
  public A leftValue() throws NoValueException {
    throw new NoValueException();
  }

  @Override
  public B rightValue() throws NoValueException {
    return b;
  }

  @Override
  public boolean equals(Object x) {
    return toString().equals(x.toString());
  }

  @Override
  public int hashCode() {
    return toString().hashCode();
  }

  @Override
  public String toString() {
    return "Right(" + b.toString() + ")";
  }

}

Lines of code: 75.

Scala

sealed trait Either[A,B]
case class Left[A,B](a: A) extends Either[A,B]
case class Right[A,B](b: B) extends Either[A,B]

Lines of code: 3.

Haskell

data Either a b = Left a | Right b deriving (Eq, Show)

Lines of code: 1.

RightBiasedEither

Either is useful for storing a value of one of two types, but it isn't very useful otherwise.

Let's make it composable by adding map, ap, and flatMap (a.k.a. fmap, <*>, and >>=), and biasing it to Right.

Java

class NoValueException extends Exception { }

interface RightBiasedEither<A,B> {

  boolean isLeft();
  boolean isRight();

  A leftValue() throws NoValueException;
  B rightValue() throws NoValueException;

  <C> RightBiasedEither<A,C> map(Function<B,C> f);
  <C> RightBiasedEither<A,C> ap(RightBiasedEither<A,Function<B,C>> fe);
  <C> RightBiasedEither<A,C> flatMap(Function<B,RightBiasedEither<A,C>> f);

}

class Left<A,B> implements RightBiasedEither<A,B> {

  public final A a;

  public Left(A a) {
    this.a = a;
  }

  @Override
  public boolean isLeft() {
    return true;
  }

  @Override
  public boolean isRight() {
    return false;
  }

  @Override
  public A leftValue() throws NoValueException {
    return a;
  }

  @Override
  public B rightValue() throws NoValueException {
    throw new NoValueException();
  }

  @Override
  public boolean equals(Object x) {
    return toString().equals(x.toString());
  }

  @Override
  public int hashCode() {
    return toString().hashCode();
  }

  @Override
  public String toString() {
    return "Left(" + a.toString() + ")";
  }

  @Override
  public <C> RightBiasedEither<A,C> map(Function<B,C> f) {
    return new Left<>(a);
  }

  @Override
  public <C> RightBiasedEither<A,C> ap(RightBiasedEither<A,Function<B,C>> fe) {
    return new Left<>(a);
  }

  @Override
  public <C> RightBiasedEither<A,C> flatMap(Function<B,RightBiasedEither<A,C>> f) {
    return new Left<>(a);
  }

}

class Right<A,B> implements RightBiasedEither<A,B> {

  public final B b;

  public Right(B b) {
    this.b = b;
  }

  @Override
  public boolean isLeft() {
    return false;
  }

  @Override
  public boolean isRight() {
    return true;
  }

  @Override
  public A leftValue() throws NoValueException {
    throw new NoValueException();
  }

  @Override
  public B rightValue() throws NoValueException {
    return b;
  }

  @Override
  public boolean equals(Object x) {
    return toString().equals(x.toString());
  }

  @Override
  public int hashCode() {
    return toString().hashCode();
  }

  @Override
  public String toString() {
    return "Right(" + b.toString() + ")";
  }

  @Override
  public <C> RightBiasedEither<A,C> map(Function<B,C> f) {
    return new Right<>(f.apply(b));
  }

  @Override
  public <C> RightBiasedEither<A,C> ap(RightBiasedEither<A,Function<B,C>> fe) {
    return fe.map((f) -> f.apply(b));
  }

  @Override
  public <C> RightBiasedEither<A,C> flatMap(Function<B,RightBiasedEither<A,C>> f) {
    return f.apply(b);
  }

}

Lines of code: 102.

Scala

trait RightBiasedEither[A,B] {
  def map[C](f: B => C): RightBiasedEither[A,C]
  def ap[C](fe: RightBiasedEither[A,B => C]): RightBiasedEither[A,C]
  def flatMap[C](f: B => RightBiasedEither[A,C]): RightBiasedEither[A,C]
}

case class Left[A,B](a: A) extends RightBiasedEither[A,B] {
  def map[C](f: B => C): RightBiasedEither[A,C] = Left(a)
  def ap[C](fe: RightBiasedEither[A,B => C]): RightBiasedEither[A,C] = Left(a)
  def flatMap[C](f: B => RightBiasedEither[A,C]): RightBiasedEither[A,C] = Left(a)
}

case class Right[A,B](b: B) extends RightBiasedEither[A,B] {
  def map[C](f: B => C): RightBiasedEither[A,C] = Right(f(b))
  def ap[C](fe: RightBiasedEither[A,B => C]): RightBiasedEither[A,C] = fe.map(f => f(b))
  def flatMap[C](f: B => RightBiasedEither[A,C]): RightBiasedEither[A,C] = f(b)
}

Lines of code: 15.

Haskell

data RightBiasedEither a b = Left a | Right b deriving (Eq, Show)

instance Functor (RightBiasedEither a) where
  fmap f e = e >>= (\x -> pure (f x))

instance Applicative (RightBiasedEither a) where
  pure b = Right b
  (<*>) fe e = fe >>= (\f -> fmap f e)

instance Monad (RightBiasedEither a) where
  (>>=) (Right b) f = f b
  (>>=) (Left a) _  = Left a

Lines of code: 9.