DEV Community

Stefan Compton
Stefan Compton

Posted on

Either In Scala

“Either is both, and Both is neither.” - Plutarch

Either is another of scala's monadic types that it might be easy to overlook when first learning the language. However, I think it combines a lot of the rationale for Scala and also Scala (and FP) idioms, and is therefore worth taking the time to become familiar with.

As a monadic type, the purpose of Either is to allow you to compose computations in a declarative, functional style.

But What Is An Either?

Either is a wrapper type for a value that can be one of two types. Suppose I want to write a method that divides two numbers. If the division succeeds then I want to return a double. If it fails, I want to return a String with a message.

In Java we can't really do this.

// can't do this
public *what?* method(int num, int denom) {
    if (denom == 0) {
        return "cannot divide by 0";
    } else {
        return (double)denom / num;
    }
}
Enter fullscreen mode Exit fullscreen mode

We could throw an exception, and let the calling code then catch it and decide what to do

public double method(int num, int denom) throws IllegalArgumentException {
    if (denom == 0) {
        throw new IllegalArgumentException("cannot divide by 0");
    } else {
        return (double)denom / num;
    }
}

...

// calling code
try {
    double res = method(1, 0);
    doSometh(res);
} catch (IllegalArgumentException iae) {
    doSomethElse(iae.getMessage());
}
Enter fullscreen mode Exit fullscreen mode

While this example is meant to be trivial and not something you'd want to do, there are a number of issues with the Java implementation imho.

  • it's verbose (well it is Java I suppose!)
  • semantically the string path has to be an exception condition - this is mostly OK as it's likely what you want to use this kind of code for.
  • IllegalArgumentException is correct from the method pov, but from the calling code it doesn't make much sense. The message it wraps is not unambiguously related to illegal arguments, it's just a different value that was returned from the method.
  • exceptions are side effects

Enter The Either

As previously mentioned, Either wraps one of two generic types. For our toy example above we could use an Either[String, Double] and the code would look like

def method(num: Int, denom: Int): Either[String, Double] = {
    if (denom == 0) Left("cannot divide by 0")
    else Right(denom.asDouble / num)
}

...

// calling code
method(1, 0) match {
    case Left(s) => // do something stringy with s
    case Right(d) => // do something doubley with d
}
Enter fullscreen mode Exit fullscreen mode

I think this nicely addresses the concerns with the Java code. It's concise and clear. The two value types are just different paths and not semantically exceptions. In fact we don't need to use an exception just to wrap the String value at all. And there are no side effects.

A More Involved Example

While the above captures some of the aspects of Either, the real power comes when running a number of computations, any of which may fail. Especially when you have Java code you need to be interoperable with that throws Exceptions. The Java model of throwing exceptions doesn't work well in the world of pure, idempotent, side-effect free functions. This is particularly problematic once you get into Akka Actor systems that can span multiple VMs or machines.

Let's take a look at how Either can help us work with Java code in a functional way.

Suppose we have a Java method that likes to throw exceptions

public int unpredictableJavaMethod(int n) {
    if (n % 2 == 0) {
        return n;
    } else {
        throw new RuntimeException("failed");
    }
}
Enter fullscreen mode Exit fullscreen mode

And suppose we have a sequence of values we need to run through this code and evaluate the results

val results = (1 to 10)
    .map(unpredictableJavaMethod)
Enter fullscreen mode Exit fullscreen mode

Unfortunately this will fail with a runtime exception on value 2 and we'll never get to the rest of the results. Rather more fortunately we can wrap our computations in Either like so

def throwableToLeft[T](block: => T): Either[java.lang.Throwable, T] =
    try {
        Right(block)
    } catch {
        case ex => Left(ex)
    }
Enter fullscreen mode Exit fullscreen mode

So now we can just wrap the java call and end up with composable operations like

val results = (1 to 10)
    .map(n => throwableToLeft(js.unpredictableJavaMethod(n)))

results.map {
    case Left(ex) => // do something with exception
    case Right(value) => value
}
Enter fullscreen mode Exit fullscreen mode

Instead of map we can fold over the results if we need to reduce it to a single value. Or filter, or use a Scala for comprehension if that syntax is easier.

Summing Up

So Either is a way of putting two types together in one, but the real power is in the way it supports integrating Java code into your Scala project in a safe and functional way.

Top comments (0)