“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;
}
}
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());
}
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
}
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");
}
}
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)
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)
}
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
}
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)