从源码层面解读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 结果进行触发;

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,039评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,426评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,417评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,868评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,892评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,692评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,416评论 3 419
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,326评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,782评论 1 316
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,957评论 3 337
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,102评论 1 350
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,790评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,442评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,996评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,113评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,332评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,044评论 2 355

推荐阅读更多精彩内容