# Option vs Either (Part 3)

06/26/20193 Min Read — In SoftwareDesign, Refactoring

This post is the last one of the Option vs Either trilogy. In the first post, I introduced the `Option` type and why it should be used over `null`. In the second post, I introduced the `Either` type and showed how it can be used, for example, to define a uniform return value for two distinct object types. This post explains the motivation why Lewis and I decided to refactor some Scala functions from return type `Option` to `Either`.

##### `Option[A]` and `Either[A,B]` - how do they relate to each other?

Let's create a function called `div` which divides a given dividend `a` by a divisor `b`. This function could be defined as follows:

`// return type: Int - throws a runtime exception if the divisor is 0def div(a: Int, b: Int): Int =  a / b`

Usually, we don't want to handle error cases with exceptions, especially in functional programming. (Note to myself: The 'why' could be a nice topic for another blog post.)

For this reason, let's redefine our `div` function and return an `Option` instead of `Int` in order to avoid exceptions. The code for this is also straightforward: If the given divisor `b` is 0, then we return `None`, otherwise, we return `Some(quotient)`.

`// return type: Option[Int] - returns None if the divisor is 0def div(a: Int, b: Int): Option[Int] =  if (b == 0) None else (a / b)`

Let's redefine our `div` function one last time to make it return an `Either[String, Int]`. In this case, if the given divisor is 0, we return a `Left` object with the error message "Division by zero is not allowed"*, or a `Right` object wrapping the quotient.

`// return type: Either[String, Int] - returns Left(errorMessage) if the divisor is 0def div(a: Int, b: Int): Either[String, Int] =  if (b == 0) Left("Division by zero is not allowed") else Right(a / b)`

Prima facie, the `Option` and `Either` version seem very similar, but there is a notable difference: When a function returns `None`, all you know is that no result value is defined, but you are not told the reason. In our example, it is pretty obvious what `None` means (namely division by zero), but there are a lot of situations which are not that obvious.

*You want to understand why division by zero is not allowed? Just ask Siri: "Hey Siri, was ist 0 geteilt durch 0" and you'll receive a brilliant explanation😂

##### Refactoring from `Option[A]` to `Either[A, B]`

When Lewis and I started our refactoring session, we had code that was roughly structured like shown as follows. We had a somehow generic `parseContent` function which dispatched the `content` to parse as well as `someMoreInfo` to a specific parse function depending on the `contentType`. The `parseContent` function returned an `Option` with `Some(parsedResult)` or `None` if the content parsing failed. In case the content type is not supported, a warning message was logged.

`def parseContent(content: String, contentType: String, someMoreInfo: MoreInfo): Option[ParsedResult] = {  contentType match {    case "html" =>       parseHtmlStuff(content, someMoreInfo)    case "xml" =>      parseXmlStuff(content, someMoreInfo)        case "xyz =>      parseXyzStuff(content, someMoreInfo)    case notSupported =>      logger.warn(s"Content type '\$notSupported' is not supported")      None  }}`

In the following snippet, you see an example of how the `parseContent` function from above was used before. We received our content to parse as well as some additional information. When the parsing succeeds, an info message is logged, otherwise, we log a warning message.

`val (content, contentType, someMoreInfo) = getAllTheStuffWeNeedToParse()parseContent(content, contentType, someMoreInfo) match {  case Some(parsedResult) =>    logger.info("Cool, it worked!")  case None =>    logger.warn("Oops, it seems that something went wrong")}`

The following snippet shows how `parseHtmlStuff` works. (The other parsing functions roughly looked the same.)

`def parseHtmlStuff(html: String, someMoreInfo: MoreInfo): Option[ParsedResult] = {  if (…) {    logger.warn("content is not a valid html format")    None  } else if (…) {    // 'someMoreInfo' is only passed for logging reasons :-/ …    logger.warn("parsing failed because of …. Some more info: " + someMoreInfo)    None  } else {    …    Some(parsedHtmlResult)  }}`

There were two things that bothered us about these parsing functions:

1. The parsing functions themselves logged warning or error messages.
2. We passed `someMoreInfo` to the parser functions only for logging reasons.

So we decided to refactor the return type: Instead of returning an `Option[ParsedResult]` and doing the logging in the parser functions, we decided to change the function's return type to `Either[String, ParsedResult]`, where `Left` contains the reason why the parsing failed, and `Right` represents the successfully parsed result. And there are a lot of things which can go wrong when parsing data (invalid data format, missing elements, …, you name it)! The type parameters `String` and `ParsedResult` for the `Either` return type are also a perfect example of two types which do not relate to each other directly (i.e. no common superclass except `Any`). Using `Either` instead of `Option` was exactly what helped us to solve the two problems discussed above:

1. If the parsing operation succeeds, we can return a `Right` object which wraps the parsed data. In case the parsing operation fails, we were now able to return a specific error message for a specific parsing failure as a `Left` result. By returning the error message in case of a failure, the parsing functions no longer have to log the messages themselves.
2. We no longer had to pass `someMoreInfo` to the parser functions for logging reasons, since the 'outer' layer could do just append the `someMoreInfo` to the `Left` error message. The outer layer, in this case, was a function which calls the function `parseContent`. Only the outermost layer now logs a given error message.

Our code base was much more complex than the example I described here and so the refactoring took us quite some time. (In addition to that, we also had to fix the tests which did not compile anymore.) At the end, when all this refactoring stuff was done, we were really happy to see the final result 😃