3

Long time back we had long debate within our team on using Try[A] vs Either[Throwable, A] and we ended up choosing Try[A] because those are semantically same when left side of Either is Throwable. That time I was searching for a codebase where Either[Throwable, A] is used but I couldn't find any.

But today while watching Fabio Labella's talk on How do Fibers Work? A Peek Under the Hood, following signature struck my mind


    def async[A](k: (Either[Throwable, A] => Unit) => Unit): F[A]

So my question is, Is there any specific reason for using Either[Throwable, A] instead of Try[A]? Which one is recommended?

Pritam Kadam
  • 2,388
  • 8
  • 16
  • 3
    Related: https://stackoverflow.com/a/29779352/2928853 – jrook Sep 25 '19 at 17:53
  • Exactly `Try[A]` is isomorphic to `Either[Throwable, A]`. My question is, Is there any reason of using `Either[Throwable, A]` or it is just individuals choice? – Pritam Kadam Sep 25 '19 at 18:32
  • 1
    this may somehow related to the fact that Try is not a Monad (almost monad but not exactly). Cats is pure functional programming library, so they probably decided to use pure functional structures. – Bogdan Vakulenko Sep 25 '19 at 18:32

2 Answers2

2

I am not sure semantics are entirely isomorphic between Try[A] and Either[Throwable, A]. For example, we can change the bias of Either

val e: Either[Throwable, Int] = Left(new Exception("boom"))
for (s <- e.left) yield s.getMessage.length
res5: scala.util.Either[Int,Int] = Left(4)

or swap the sides

e.swap
res6: scala.util.Either[Int,Throwable] = Right(java.lang.Exception: boom)

Consider some unusual scenario where we consider Throwables to actually be the expected result and hence would want them on the Right side. Perhaps we are building some pure-functional testing library where we want to provide an API for asserting on exceptions. One could imagine encoding these semantics as Either[A, Throwable].

Another potential advantage of Either over Try might be cats providing more extension methods for it. For example, consider all the goodies in import cats.syntax.either._ whilst I am not aware of similar for Try. Say we want to accumulate all the errors in a list instead of short-circuit, then we can simply traverse(_.toValidatedNec) like so

import cats.syntax.either._
import cats.instances.list._
import cats.syntax.traverse._

val l: List[Either[Throwable, Int]] = List(Right(1), Right(2), Left(new RuntimeException("boom")), Right(3), Left(new RuntimeException("crash")))
l.traverse(_.toValidatedNec)
res0: cats.data.ValidatedNec[Throwable,List[Int]] = Invalid(Chain(java.lang.RuntimeException: boom, java.lang.RuntimeException: crash))

To achieve similar with Try I think we would have to work harder.

Mario Galic
  • 47,285
  • 6
  • 56
  • 98
0

Skimming the answers from this question, here is a list of differences:

  • Either does not imply success and failure, it is just a container for either an A or a B
  • Use Try to clearly identify a potential failure in the computation, the failure being represented by an exception
  • What is nice about Try[X] is that you can chain futher operations to it, if it is really a Success they will execute, if it was a Failure they won't
  • Try has lots of other useful methods, and of course its companion apply method, that make it very convenient for its intended use - exception handling.
  • Either can be used to specify a description of the error, which can be shown to the client. Try - wraps an exception with a stack trace
  • Good candidates for Try are methods, that might throw an exception, or methods, that sends request to external systems
jrook
  • 3,459
  • 1
  • 16
  • 33