从源码层面解读Either、Option 和 Try

scala

差异

  • Either

代表一个结果的两个可能性,一个是 Right ,一个是 Left

  • Option

代表可选择的值,一个是 Some(代表有值),一个是 None (值为空);常用于结果可能为 null 的情况;

  • Try

运算的结果有两种情况,一个是运行正常,即 Success ,一个是运行出错,抛出异常 ,即 Failure ,其中 Failure 里面包含的是异常的信息;

共同点

三者都存在两种可能性的值;都可以在结果之上进行 mapflatMap 等操作;


  • Either

RightLeft 是继承自 Either 的两个 case 类;

//Left
final case class Left[+A, +B](@deprecatedName('a, "2.12.0") value: A) extends Either[A, B]

//Right
final case class Right[+A, +B](@deprecatedName('b, "2.12.0") value: B) extends Either[A, B]

Eihter 代表一个结果的两个可能性,一个是 Right ,一个是 Left ;

import scala.io.StdIn._
val in = readLine("Type Either a string or an Int: ")
val result: Either[String,Int] =
  try Right(in.toInt)
  catch {
    case e: NumberFormatException => Left(in)
  }
result match {
  case Right(x) => s"You passed me the Int: $x, which I will increment. $x + 1 = ${x+1}"
  case Left(x)  => s"You passed me the String: $x"
}

Either 是偏向 Right 值的,在 Either 使用 mapflatMap 等操作时,只有 Either 的结果是 Right 时,才会触发操作;习惯性地将Left 值代表不好的结果(失败的结果),Right 代表好的结果(成功的结果);

def doubled(i: Int) = i * 2
Right(42).map(doubled) // Right(84)
Left(42).map(doubled)  // Left(42)

由于Either 定义了 flatMapmap ,所以可以对 Either 使用 for comprehensions

val right1 = Right(1)   : Right[Double, Int] //确定right1的类型
val right2 = Right(2)
val right3 = Right(3)
val left23 = Left(23.0) : Left[Double, Int]  //确定left23的类型
val left42 = Left(42.0)
for {
  x <- right1
  y <- right2
  z <- right3
} yield x + y + z // Right(6)
for {
  x <- right1
  y <- right2
  z <- left23
} yield x + y + z // Left(23.0)
for {
  x <- right1
  y <- left23
  z <- right2
} yield x + y + z // Left(23.0)

但是不支持使用守卫表达式

for {
  i <- right1
  if i > 0
} yield i
// error: value withFilter is not a member of Right[Double,Int]

同样,下面也是不支持的

for (x: Int <- right1) yield x
// error: value withFilter is not a member of Right[Double,Int]

由于 for comprehensions 使用 mapflatMap ,所以必须要推导参数的类型,并且该类型必须是 Either ;特别的地方在于,由于Either 是偏向Right 的,所以是对于Either的值为Left必须要指定其类型,否则,该位置的默认类型为Nothing

for {
  x <- left23
  y <- right1
  z <- left42  // type at this position: Either[Double, Nothing]
} yield x + y + z
//            ^
// error: ambiguous reference to overloaded definition,
// both method + in class Int of type (x: Char)Int
// and  method + in class Int of type (x: Byte)Int
// match argument types (Nothing)
for (x <- right2 ; y <- left23) yield x + y  // Left(23.0)
for (x <- right2 ; y <- left42) yield x + y  // error
for {
  x <- right1
  y <- left42  // type at this position: Either[Double, Nothing]
  z <- left23
} yield x + y + z
// Left(42.0), but unexpectedly a `Either[Double,String]`
  • Option

SomeNone 是继承自 Option 的两个 case 类;

//Some
final case class Some[+A](@deprecatedName('x, "2.12.0") value: A) extends Option[A]

//None
case object None extends Option[Nothing]

Option 的习惯用法是把它当作集合或者monad ,通过mapflatMapfilterforeach

//方式一
val name: Option[String] = request getParameter "name"
val upper = name map { _.trim } filter { _.length != 0 } map { _.toUpperCase }
println(upper getOrElse "")

//方式一等价于方式二
val upper = for {
  name <- request getParameter "name" //由于For表达式的作用,如何此处返回None,那么整个表达式将返回None
  trimmed <- Some(name.trim)
  upper <- Some(trimmed.toUpperCase) if trimmed.length != 0
} yield upper
println(upper getOrElse "")

另外一个习惯用法是(不太推荐)通过模式匹配:

val nameMaybe = request getParameter "name"
nameMaybe match {
  case Some(name) =>
    println(name.trim.toUppercase)
  case None =>
    println("No name value")
}
  • Try

FailureSuccess 是继承自 Try 的两个 case 类;

//Failure
final case class Failure[+T](exception: Throwable) extends Try[T]

//Success
final case class Success[+T](value: T) extends Try[T]

Try 常用于那些存在异常的地方,通过Try 不用确定地对可能出现的异常进行处理;

import scala.io.StdIn
import scala.util.{Try, Success, Failure}
def divide: Try[Int] = {
  val dividend = Try(StdIn.readLine("Enter an Int that you'd like to divide:\n").toInt)
  val divisor = Try(StdIn.readLine("Enter an Int that you'd like to divide by:\n").toInt)
  val problem = dividend.flatMap(x => divisor.map(y => x/y))
  problem match {
    case Success(v) =>
      println("Result of " + dividend.get + "/"+ divisor.get +" is: " + v)
      Success(v)
    case Failure(e) =>
      println("You must've divided by zero or entered something that's not an Int. Try again!")
      println("Info from the exception: " + e.getMessage)
      divide
  }
}

在上面的例子中,可以看出 Try 的一个重要的特性,就是Try 具有管道的功能 ,flatMapmap 将那些成功完成的操作的结果包装成Success ,将那些异常包装成 Failure ,而对于 recoverrecoverWith 则是默认对 Failure 结果进行触发;

你可能感兴趣的:(从源码层面解读Either、Option 和 Try)