54

Option monad is a great expressive way to deal with something-or-nothing things in Scala. But what if one needs to log a message when "nothing" occurs? According to the Scala API documentation,

The Either type is often used as an alternative to scala.Option where Left represents failure (by convention) and Right is akin to Some.

However, I had no luck to find best practices using Either or good real-world examples involving Either for processing failures. Finally I've come up with the following code for my own project:

    def logs: Array[String] = {
        def props: Option[Map[String, Any]] = configAdmin.map{ ca =>
            val config = ca.getConfiguration(PID, null)
            config.properties getOrElse immutable.Map.empty
        }
        def checkType(any: Any): Option[Array[String]] = any match {
            case a: Array[String] => Some(a)
            case _ => None
        }
        def lookup: Either[(Symbol, String), Array[String]] =
            for {val properties <- props.toRight('warning -> "ConfigurationAdmin service not bound").right
                 val logsParam <- properties.get("logs").toRight('debug -> "'logs' not defined in the configuration").right
                 val array <- checkType(logsParam).toRight('warning -> "unknown type of 'logs' confguration parameter").right}
            yield array

        lookup.fold(failure => { failure match {
            case ('warning, msg) => log(LogService.WARNING, msg)
            case ('debug, msg) =>   log(LogService.DEBUG, msg)
            case _ =>
        }; new Array[String](0) }, success => success)
    }

(Please note this is a snippet from a real project, so it will not compile on its own)

I'd be grateful to know how you are using Either in your code and/or better ideas on refactoring the above code.

Alexander Azarov
  • 12,235
  • 2
  • 45
  • 50
  • 2
    I can find no mention of it whatsoever in Odersky's book, either. – skaffman Jul 28 '09 at 12:35
  • 4
    Yes, I have "Programming in Scala" and could not find any mention of Either in there. The best analogy I know is Box in Liftweb which is used for the purpose of carrying failures as well -- it's like Option, but with extra functionality. – Alexander Azarov Jul 28 '09 at 13:16
  • Any better alternative to `Option[Either[Foo, Bar]]` ? – Jus12 May 05 '14 at 12:59

4 Answers4

54

Either is used to return one of possible two meaningful results, unlike Option which is used to return a single meaningful result or nothing.

An easy to understand example is given below (circulated on the Scala mailing list a while back):

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

As the function name implies, if the execution of "block" is successful, it will return "Right(<result>)". Otherwise, if a Throwable is thrown, it will return "Left(<throwable>)". Use pattern matching to process the result:

var s = "hello"
throwableToLeft { s.toUpperCase } match {
  case Right(s) => println(s)
  case Left(e) => e.printStackTrace
}
// prints "HELLO"

s = null
throwableToLeft { s.toUpperCase } match {
  case Right(s) => println(s)
  case Left(e) => e.printStackTrace
}
// prints NullPointerException stack trace

Hope that helps.

Tom Green
  • 3
  • 2
Walter Chang
  • 11,279
  • 2
  • 44
  • 36
  • 5
    Peculiar... why not just throw the exception? – skaffman Jul 28 '09 at 14:42
  • 10
    Having exception handling code all over the place is ugly and hard to manage. Use throwableToLeft turns exception handling into pattern matching which, imho, easier to read and maintain. – Walter Chang Jul 28 '09 at 14:51
  • 24
    For example, you may have several actors doing different calculations concurrently, some of which actually return a result, and some throw an exception. If you just throw the exception, some of those actors may not have started working yet, you lose results from any actors who haven't finished yet, etc. With this approach, all actors will return a value (some `Left`, some `Right`) and it ends up being much easier to handle. – Alexey Romanov Jul 28 '09 at 14:55
  • 2
    @skaffman Also, you cannot "send an exception", say from one actor to another. This allows sending exceptions across. – Jus12 Sep 11 '12 at 12:33
  • 7
    In Scala 2.10 there is the `scala.util.Try` class which is essentially a special case of `Either` for exception handling, so if you're doing anything like the example above then that's something to consider. – Nick Oct 02 '13 at 11:47
  • @skaffman I prefer Either's to exceptions in Scala because unlike Java, Scala's exceptions are unchecked. Eithers, on the other hand, must be acknowledged in order to use the value. – Tejas Manohar Mar 18 '17 at 22:55
14

Scalaz library has something alike Either named Validation. It is more idiomatic than Either for use as "get either a valid result or a failure".

Validation also allows to accumulate errors.

Edit: "alike" Either is complettly false, because Validation is an applicative functor, and scalaz Either, named \/ (pronounced "disjonction" or "either"), is a monad. The fact that Validation can accumalate errors is because of that nature. On the other hand, / has a "stop early" nature, stopping at the first -\/ (read it "left", or "error") it encounters. There is a perfect explanation here: http://typelevel.org/blog/2014/02/21/error-handling.html

See: http://scalaz.googlecode.com/svn/continuous/latest/browse.sxr/scalaz/example/ExampleValidation.scala.html

As requested by the comment, copy/paste of the above link (some lines removed):

// Extracting success or failure values
val s: Validation[String, Int] = 1.success
val f: Validation[String, Int] = "error".fail

// It is recommended to use fold rather than pattern matching:
val result: String = s.fold(e => "got error: " + e, s => "got success: " + s.toString)

s match {
  case Success(a) => "success"
  case Failure(e) => "fail"
}

// Validation is a Monad, and can be used in for comprehensions.
val k1 = for {
  i <- s
  j <- s
} yield i + j
k1.toOption assert_≟ Some(2)

// The first failing sub-computation fails the entire computation.
val k2 = for {
  i <- f
  j <- f
} yield i + j
k2.fail.toOption assert_≟ Some("error")

// Validation is also an Applicative Functor, if the type of the error side of the validation is a Semigroup.
// A number of computations are tried. If the all success, a function can combine them into a Success. If any
// of them fails, the individual errors are accumulated.

// Use the NonEmptyList semigroup to accumulate errors using the Validation Applicative Functor.
val k4 = (fNel <**> fNel){ _ + _ }
k4.fail.toOption assert_≟ some(nel1("error", "error"))
fanf42
  • 1,638
  • 13
  • 19
  • 2
    It would be nice to see an example here in the answer. Applied to the type of problems raised here in the question . – Alexander Azarov Feb 14 '11 at 10:22
  • 2
    `flatMap`, and thus for comprehension on `Validation` has been deprecated in Scalaz 7.1 and removed in Scalaz 7.1. The examples in the answer no longer work. See [deprecation discussion](https://groups.google.com/forum/#!topic/scalaz/Wnkdyhebo2w) – kostja Jan 07 '16 at 10:09
7

The snippet you posted seems very contrived. You use Either in a situation where:

  1. It's not enough to just know the data isn't available.
  2. You need to return one of two distinct types.

Turning an exception into a Left is, indeed, a common use case. Over try/catch, it has the advantage of keeping the code together, which makes sense if the exception is an expected result. The most common way of handling Either is pattern matching:

result match {
  case Right(res) => ...
  case Left(res) => ...
}

Another interesting way of handling Either is when it appears in a collection. When doing a map over a collection, throwing an exception might not be viable, and you may want to return some information other than "not possible". Using an Either enables you to do that without overburdening the algorithm:

val list = (
  library 
  \\ "books" 
  map (book => 
    if (book \ "author" isEmpty) 
      Left(book) 
    else 
      Right((book \ "author" toList) map (_ text))
  )
)

Here we get a list of all authors in the library, plus a list of books without an author. So we can then further process it accordingly:

val authorCount = (
  (Map[String,Int]() /: (list filter (_ isRight) map (_.right.get))) 
   ((map, author) => map + (author -> (map.getOrElse(author, 0) + 1)))
  toList
)
val problemBooks = list flatMap (_.left.toSeq) // thanks to Azarov for this variation

So, basic Either usage goes like that. It's not a particularly useful class, but if it were you'd have seen it before. On the other hand, it's not useless either.

Daniel C. Sobral
  • 284,820
  • 82
  • 479
  • 670
2

Cats has a nice way to create an Either from exception-throwing code:

val either: Either[NumberFormatException, Int] =
  Either.catchOnly[NumberFormatException]("abc".toInt)
// either: Either[NumberFormatException,Int] = Left(java.lang.NumberFormatException: For input string: "abc")

in https://typelevel.org/cats/datatypes/either.html#working-with-exception-y-code

vlfig
  • 335
  • 1
  • 12