Default value on Left

2019-08-08 17:04发布

问题:

I make a call to external function and in return have several Either. Say I have

val a = Right("hey")
val b = Right(2)
val c = Left("oops") .....

for{
 x <- a.right
 y <- b.right
 z <- c.right
} yield(User(x,y,z))

But say, if z is a Left as above. Then I wish to give it a default String. i.e. by

for{
 x <- a.right
 y <- b.right
 z <- c.right.getOrElse(Right("default String")).right
} yield(User(x,y,z))

It is dirty. How can I reduce this: c.right.getOrElse(Right("default String")).right. Doing c.right.getOrElse("default") will not work as a map on String returns IndexedSeq.

回答1:

A simplified syntax can be defined by right-biasing Either implicitly, as suggested in this thread

val user = {
  implicit def rightBiasEither[A, B](e: Either[A, B]): Either.RightProjection[A,B] =
    e.right

  for {
    x <- a
    y <- b
    z <- c getOrElse (Right("default string"))
  } yield User(x, y, z)
}

You can choose where to explicitly have right-biasing using a limiting scope, as in the example code above, or wrapping the conversion in a custom object to import at will, as is customary.



回答2:

You could write a function to do this:

def rightOrElse[A, B](e1: Either[A, B], e2: => Either[A, B]): Either[A, B] =
  e1.right.flatMap(_ => e2)

You'd still need to call .right on the result, to make the types match in your for-comprehension.

This is much easier to do with Scalaz's \/ (disjunction) type (an improved version of Either). Not only is it right-biased, avoiding the need to work through a right projection, but it has a richer API including an orElse method.

val a = \/.right("hey")
val b = \/.right(2)
val c = \/.left("oops")

for {
  x <- a
  y <- b
  z <- c orElse \/.right("default String")
} yield User(x, y, z)


回答3:

An alternative is to use scala.util.Try. In short, Try is like an Either that would both be right biased and force the left type to Throwable. If your left type can easily be mapped to instances of Throwable then this is a good candidate.

Your first example would then translate to:

val a = Try("hey")
val b = Try(2)
val c = Try(sys.error("oops"))

for{
 x <- a
 y <- b
 z <- c
} yield(User(x,y,z))

To provide a default value, just use orElse:

val a = Try("hey")
val b = Try(2)
val c = Try(sys.error("oops"))

for{
 x <- a
 y <- b
 z <- c.orElse(Try("default String"))
} yield(User(x,y,z))


回答4:

A possible workaround is to consider that you can skip the for-comprehension for the last variable:

val z = c.fold(Function.const("default string"), identity)

or

val z = c.right.getOrElse("default string")

and then:

for{
 x <- a.right
 y <- b.right
} yield(User(x,y,z))


标签: scala