可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
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))