Scala

scala.util.Tryを使いこなそう

More than 1 year has passed since last update.

1.とっても便利なTry

Scalaを使っていて、以下のような書き方をしてしまうことあるのでは無いでしょうか?

def sampleDBAccess(id:String):writeEntity = {
    try{
      //何か転けそうなselect
      DBAccessor.selectById(id)
    }catch{
      case notfound:EntityNotFoundException => Entity.empty
      case e:Throwable => throw e
    }
}

JavaとかCとかやってるとつい書いてしまう処理です。
これだと、Exception投げる処理が分散してしまって、どこでなげたっけ?あれ??となってしまったりします。
また、関数型的にかっこよくないなーって感じるかもしれません。
そこで!Scalaのscala.util.Tryです!

def sampleDBAccess(id:String):Try[writeEntity] = Try {
   DBAccessor.selectById(id).recover {case notfound:EntityNotFoundException => Success(Entity.empty) } 
}

こんな感じ。
TryはSuccess()とFailure()に分かれていて、こけたらFailureに勝手にExceptionいれてくれる優れものです。

2.実際にどうやって使うの?

サンプルを使って、実際に書いてみます。

2-1. 参照したデータを使う。

// ユーザー名からEntityを引っ張ってくる関数
def resolveByName(name:String):Try[User]

// 使い方
val ids = resolveByName("yusuke").map(_.id) // Try[id]がかえる。

// キャッチする場所
val ids = ids.recover({
    //特定のExceptionの場合復帰させるのであればここに書く
    case e:Throwable => throw e 
}).get

2-2. 参照したデータでさらに参照してみる。

// ユーザー名からEntityを引っ張ってくる関数
def resolveByName(name:String):Try[User]

def resolveByUserId(id:UserId):Try[UserData]

// 使い方
val ids = resolveByName("yusuke").flatMap(user =>  resolveByUserId(user.id)) // Try[UserData]がかえる。

Try[Try[xxxx]]となった場合にはSeq同様にFlatMapを使って1つにまとめることができます。
これを十分活用すれば、Exceptionをまとめて上の層に渡すことも可能です。

2-3 こけてもこけてないように見せたい

// ユーザー名からEntityを引っ張ってくる関数
def resolveByName(name:String):Try[User]

// 上記でもかきましたがgetOrElseでもみ消しましょう。
resolveByName("hoge").getOrElse(/*デフォルト値*/)

2−4 複数のTryを扱う。

def resolveByName(name:String):Try[User]

def createByUserId(id:UserId):Try[Message]

val message = for{
   u <- resolveByName("hoge")
   message <- createByUserId(u.id)
} yield message
//messageにTry[Message]がはいる。

3. 最後に

このような感じで、scala.util.Tryはコケても、アプリケーションまでコケさせなくしたり、任意にコケさせたりできます。
いままで、try-catchで書いていた人もでscala.util.Tryを使ってスッキリさせませんか?