/var/log/jsoizo

メモ帳 技術とか趣味とか

Try,Option,EitherのorElse

Pull Requestでレビューをしていてこういう実装を見かけた。
fooで失敗したらbar, barで失敗したらbazを呼び出したい。

  def foo(s: String): String = ???
  def bar(s: String): String = ???
  def baz(s: String): String = ???

  val input: String = "awesome text"
  val result: String = Try {
    foo(input)
  } match {
    case Success(value) => value
    case Failure(_) => {
      Try {
        bar(input)
      } match {
        case Success(value) => value
        case Failure(_) => {
          Try {
            baz(input)
          } match {
            case Success(value) => value
            case Failure(error) => throw error
          }
        }
      }
    }
  }

意図は分かりやすいがインデントが深いので以下のように修正した。
Tryを合成しているということがひと目でわかるようになった。
orElsegetOrElse と違い中身を取り出さずにデフォルトの挙動を定義する。
今回のようにデフォルトの挙動を多段に組んで最後に取り出したいようなケースでは有用である。

  val resultTry: Try[String] = Try(foo(input))
    .orElse(Try(bar(input)))
    .orElse(Try(baz(input)))
  val result: String = resultTry match {
    case Success(value) => value
    case Failure(error) => throw error
  }

上記以外にもOption, EitherにもorElseメソッドがあり、Optionのscaladocが分かりやすいので引用。
ちょうど最初に挙げた例のように"パターンマッチで取り出すのと同じですよ" と書いてくれている。

  /** Returns this $option if it is nonempty,
   *  otherwise return the result of evaluating `alternative`.
   *
   * This is equivalent to:
   * {{{
   * option match {
   *   case Some(x) => Some(x)
   *   case None    => alternative
   * }
   * }}}
   *  @param alternative the alternative expression.
   */
  @inline final def orElse[B >: A](alternative: => Option[B]): Option[B] =
    if (isEmpty) alternative else this

これまであまり使うシチュエーションが無く初めて使ったが、
Javaの実装をラップしているときなんかは割と使うことが多いのだろうと感じる。

なお、orElseの引数はTry,Option,Eitherでそれぞれ名前が異なっているがやってることはどれも同じようなものである。

  • Try.orElse[U >: T](default: => Try[U]): Try[U]
  • Option.orElse[B >: A](alternative: => Option[B]): Option[B]
  • Either.orElse[A1 >: A, B1 >: B](or: => Either[A1, B1]): Either[A1, B1]