Scala Either, Left And Right

Scala中的Either是一个有趣的类,它代表两个可能的类型的其中一个类型的值。这两个值之间没有交集。
Either是抽象类,有两个具体的实现类: LeftRight
Either可以作为scala.Option的替换,可以用scala.util.Left替换scala.None,用scala.Right替换scala.Some
一般在时实践中使用scala.util.Left作为Failure,而scala.util.Right作为成功的值。
另一个类似的类是scala.util.Try

举个例子:

1
2
3
4
5
6
7
8
9
10
11
12
val in = Console.readLine("Type Either a string or an Int: ")
val result: Either[String,Int] = try {
Right(in.toInt)
} catch {
case e: Exception =>
Left(in)
}
println( result match {
case Right(x) => "You passed me the Int: " + x + ", which I will increment. " + x + " + 1 = " + (x+1)
case Left(x) => "You passed me the String: " + x
})

如果输入的值可以转换成Int类型,则结果result为Right(int),否则result为Left(String)
你可以使用match处理结果。

Either类型还提供了投影(projection)操作。根据类型是Left还是Right返回不同的结果。
例如,使用一个函数转换Either类型的值时,如果值是Left,应用left投影,然后调用map返回处理的结果,而应用right投影只是返回Left,如:

1
2
3
4
val l: Either[String, Int] = Left("flower")
val r: Either[String, Int] = Right(12)
l.left.map(_.size): Either[Int, Int] // Left(6)
l.right.map(_.toDouble): Either[String, Double] // Left("flower")

同样地, 对Right值进行left投影,只是返回Right,不做改变,只会对right投影起作用:

1
2
3
4
val l: Either[String, Int] = Left("flower")
val r: Either[String, Int] = Right(12)
r.left.map(_.size): Either[Int, Int] // Right(12)
r.right.map(_.toDouble): Either[String, Double] // Right(12.0)

既然定义了map操作,就可以使用for语句:

1
for (s <- l.left) yield s.size // Left(6)

Either除了left,right投影外,还提供一些其它的方法:

  • fold: l.fold(x => x.size, y => y.toDouble);r.fold(x => x.size, y => y.toDouble)
  • swap: l.swap //Right(flower)
  • merge: l.merge == "flower"
  • joinLeft: LeftEither[Int, String], String).joinLeft
  • joinRight: Either[A, Either[A, C]]

在Scala中使用Future,经常会处理Future的最终结果,要不成功,要不失败,这里就使用了Either:

1
2
3
4
5
6
7
val f: Future[List[String]] = future {
session.getRecentPosts
}
f onComplete {
case Right(posts) => for (post <- posts) render(post)
case Left(t) => render("An error has occured: " + t.getMessage)
}

前面提到Try也是两个值,Success和Failure,它可以和Either进行转换:

1
2
3
4
5
6
7
8
9
object Helper{
implicit class EitherPimp[L <: Throwable,R](e:Either[L,R]){
def toTry:Try[R] = e.fold(Failure(_), Success(_))
}
implicit class TryPimp[T](t:Try[T]){
def toEither:Either[Throwable,T] = t.transform(s => Success(Right(s)), f => Success(Left(f))).get
}
}

或者

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import scala.util.{ Either, Failure, Left, Right, Success, Try }
implicit def eitherToTry[A <: Exception, B](either: Either[A, B]): Try[B] = {
either match {
case Right(obj) => Success(obj)
case Left(err) => Failure(err)
}
}
implicit def tryToEither[A](obj: Try[A]): Either[Throwable, A] = {
obj match {
case Success(something) => Right(something)
case Failure(err) => Left(err)
}

参考文档

  1. http://www.scala-lang.org/files/archive/nightly/docs/library/index.html#scala.util.Either
  2. http://danielwestheide.com/blog/2013/01/02/the-neophytes-guide-to-scala-part-7-the-either-type.html
  3. http://stackoverflow.com/questions/22532357/either-to-try-and-visa-versa-in-scala
评论