CLOVER🍀

That was when it all began.

Scala 2.10.0 Try & NonFatal

なんか、情報だけチラチラ見かけていて気になっていたので。まあ、半分くらいFutureの時に使っているのですが。

Try

Futureの結果として使っていた、SuccessとFailureの親クラスです。Successが成功、Failureが失敗を表すわけですが、Failureは例外を情報として持ちます。それ以外はSuccessですと。

Futureの時は、計算結果がSuccessまたはFailureとして得られましたが、これを単独で使う場合にはTryコンパニオンオブジェクトのapplyメソッドを使用します。

println(Try { 10 })  // => Success(10)
println(Try { throw new Exception("Oops!") })  // => Failure(java.lang.Exception: Oops!)

Try.applyのシグネチャ

def apply[T](r: ⇒ T): Try[T]

なので、引数は一応関数が渡せますよ、と。結果は、処理が成功すればSuccessが、例外がスローされた場合は、NonFatalなもの以外はFailureが返り、NonFatalなものはそのままスローされます。NonFatalなものというのは、また後で…。

使い方はFutureみたいな感じで、map、flatMap、foreachなどを備えているので、for式で使えますし(filterはありますけど、withFilterはないみたいですが…)、recover、recoverWithもあるので、Failureからの回復もOKです。
その他、getやgetOrElseもあるので…なんとなくプログラミングスタイルが見えてくる感じですね。

コード例をちょこちょこと貼っておきます。

import scala.util.{Failure, Success, Try}

println(Try { 10 })  // => Success(10)
println(Try { throw new Exception("Oops!") })  // => Failure(java.lang.Exception: Oops!)

println(Try { 10 }.isSuccess)  // => true
println(Try { 10 }.isFailure)  // => false
println(Try { throw new Exception("Oops!") }.isSuccess)  // => false
println(Try { throw new Exception("Oops!") }.isFailure)  // => true

println(Try { 10 }.get)  // => 10
try {
  Try { throw new Exception("Oops!") }.get
} catch {
  case e: Exception => println(e)  // => java.lang.Exception: Oops!
}

println(Try { 10 }.getOrElse(50))  // => 10
println(Try { throw new Exception("Oops!") }.getOrElse(50))  // => 50

Try { 10 } foreach println  // => 10
Try { throw new Exception("Oops!") } foreach println  // => 何も出力されない

Try { 10 } map { _ * 2 } foreach println
// => 20
Try[Int] { throw new Exception("Oops!") } map { _ * 2 } foreach println
// => 何も出力されない

println(Try { 40 } recover { case th => 20 })
// => Success(40)
println(Try[Int] { throw new NullPointerException("Oops!") }
  .recover { case e: NullPointerException => 50 })
// => Success(50)
println(Try[Int] { throw new NullPointerException("Oops!") }
  .recover { case e: IllegalArgumentException => 50 })
// => Failure(java.lang.NullPointerException: Oops!)

println(Try { 40 } recoverWith { case th => Success(20) })
// => Success(40)
println(Try { 40 } recoverWith { case th => Failure(new Exception("Failure?")) })
// => Success(40)
println(Try[Int] { throw new NullPointerException("Oops!") }
  .recoverWith { case e: NullPointerException => Success(50) })
// => Success(50)
println(Try[Int] { throw new NullPointerException("Oops!") }
  .recoverWith { case e: IllegalArgumentException => Success(50) })
// => Failure(java.lang.NullPointerException: Oops!)
println(Try[Int] { throw new NullPointerException("Oops!") }
  .recoverWith { case e: NullPointerException => Failure(new Exception("Failure?")) })
// => Failure(java.lang.Exception: Failure?)
println(Try[Int] { throw new NullPointerException("Oops!") }
  .recoverWith { case e: IllegalArgumentException => Failure(new Exception("Failure?")) })
// => Failure(java.lang.NullPointerException: Oops!)

for {
  n1 <- Try { 10 }
  n2 <- Try { 20 }
} println(n1 + n2)  // => 30

for {
  n1 <- Try { 10 }
  n2 <- Try[Int] { throw new Exception("Oops!") }
} println(n1 + n2)  // => 何も出力されない

Try { 10 } map { _ * 20 } match {
  case Success(n) => println(s"Success? => $n")  // => Success? => 200
  case Failure(e) => println(s"Failure? => $e")
}

Try[Int] { throw new Exception("Oops!") } map { _ * 20 } match {
  case Success(n) => println(s"Success? => $n")
  case Failure(e) => println(s"Failure? => $e")  // => Failure? => java.lang.Exception: Oops!
}

NonFatal

Try.applyでNonFatalなもの以外は、と書きましたが、NonFatalは致命的なエラーかそうでないかを判定するオブジェクトです。NonFatalなので、致命的なエラーでない例外の場合に真となります。

致命的なエラーとは、

  • VirtualMachineErrorのサブクラス(例えばOutOfMemoryError、ただしStackOverflowErrorは除く)
  • ThreadDeath
  • LinkageError
  • InterruptedException
  • NotImplementedError

と定義されています。

この他、scala.util.control.ControlThrowableもNonFatalではないと判定されますが、これはScalaでフロー制御を行うための例外で、これまでキャッチされては困るのでNonFatalではないと扱われているようです。

それ以外は、パターンマッチでNonFatalの結果がtrueとなります。

import scala.util.control.NonFatal

try {
  throw new OutOfMemoryError("dummy")
} catch {
  case NonFatal(e) => println(s"Non Fatal [$e]")
  case th: Throwable => println(s"Cached! [$th]")  // => Cached! [java.lang.OutOfMemoryError: dummy]
}

try {
  throw new NullPointerException("dummy")
} catch {
  case NonFatal(e) => println(s"Non Fatal [$e]")  // => Non Fatal [java.lang.NullPointerException: dummy]
  case th: Throwable => println(s"Cached! [$th]")
}
  }
}

OutOfMemoryErrorはNonFatalとは判定されていませんが、NullPointerExceptionはNonFatalであると判定されています。

Scala 2.10.0からはcatch節で

} catch {
  case th => ...
}

みたいにパターンマッチをかける時に例外の型を書かないと(Throwableとして捕らえるつもりであっても)警告されるようになったので、Scalaでは活用した方がいいんでしょうね。

ところで、NonFatal.apply、unapplyの定義ってこうなっているのですが

   def apply(t: Throwable): Boolean = t match {
     case _: StackOverflowError => true // StackOverflowError ok even though it is a VirtualMachineError
     // VirtualMachineError includes OutOfMemoryError and other fatal errors
     case _: VirtualMachineError | _: ThreadDeath | _: InterruptedException | _: LinkageError | _: ControlThrowable | _: NotImplementedError => false
     case _ => true
   }
  /**
   * Returns Some(t) if NonFatal(t) == true, otherwise None
   */
  def unapply(t: Throwable): Option[Throwable] = if (apply(t)) Some(t) else None

パターンマッチで、こんなのORの書き方もあったんですね…。