Help us understand the problem. What is going on with this article?

scala.util.Tryをパターンにわけてうまく実装するぞ

More than 3 years have passed since last update.

主にWebアプリケーションを書くときに、エラーやValidation結果を返すのに、いろいろと悩むことがありますよね。

Option?
Either?
Try?
\/?

で最近は、けっこーTry一択で実装してて、なんとなしにパターン的なものが掴めてきた気がするので、自分用も兼ねてまとめてみた。

Try型は低レイヤーのメソッドで生成しよう

最も小さいメソッドでTryを生成し、そのメソッドを呼び出すメソッド内では、for式を使うと良い。

lazy val assertNotEmpty: String => Try[Unit] = (value: String) => Try {
  assert(value.nonEmpty, "value is empty")
}

lazy val findBy: String => Try[String] = (id: String) => Try {
  someTable.findBy(id)
}

def printSomething(id: String): Try[Unit] =
  for {
    u <- assertNotEmpty(id)
    some <- findBy(id)
  } yield
    println(some)

みたいな。

Try.apply で処理を受けてやるのは、小さい粒度?のメソッドでやってやって、そいつを呼ぶメソッドでは、for式で。

こうすると、スッキリとしたコードが書けて良いなって思ってる。

これを守らなかったら、

lazy val assertNotEmpty: String => Unit = (value: String) =>
  assert(value.nonEmpty, "value is empty")

lazy val findBy: String => String = (id: String) =>
  someTable.findBy(id)

def printSomething(id: String): Try[Unit] = Try {
  assertNotEmpty(id)
  val result = findBy(id)
  println(result)
}

こんな感じになって、なんか、valとか入るし、printSomethingの実装のしかたがあんまり決まってこないし、なんかビミョー。

って気がする。

小さい単位でTry型を返すようにしてやると、そいつを呼び出すところの処理がある程度、決まってくるから、なんか書いてて気持ちいい。

仕様が変わったり、処理を追加したりにも柔軟に対応できる。

たとえば、updateしてから、printしてって仕様に変わったとしても、

lazy val update = (id: String) =>  (something: String) => Try {
  someTable.update(id)(something)
}

って関数を追加してやって、for式に一行呼び出しを書くんだろうなってすぐに分かる。

def printSomething(id: String): Try[Unit] =
  for {
    u <- assertNotEmpty(id)
    some <- findBy(id)
    updatedSome <- update(id)(some + "thing") // <= ココに追加したった。
  } yield
    println(updatedSome)

処理の流れも見易くて、Gitとかで差分見ても、一目瞭然。

やっぱこの単位でTryにしていくのがベストな気がします。

Try[Seq[Try[String]]]をほぐす

低レイヤーのメソッドでTryにしていくと、 Try[Seq[Try[String]]] みたいなことになりがち。

lazy val assertNotEmpty: String => Try[Unit] = (value: String) => Try {
  assert(value.nonEmpty, "value is empty")
}

lazy val findAll: () => Try[Seq[String]] = () => Try {
  someTable.findAll
}

lazy val findBy: String => Try[String] = (id: String) => Try {
  someTable.findBy(id)
}

def printSomething(): Try[Seq[Try[String]]] =
  for {
    ids <- findAll()
  } yield for {
    id <- ids
  } yield for {
    u <- assertNotEmpty(id)
    some <- findBy(id)
  } yield
    some

上記のよーな感じ。これをほぐす。

def printSomething(): Try[Seq[String]] =
  for {
    ids <- findAll()
    somes <- Try {
      for {
        id <- ids
      } yield (for {
        u <- assertNotEmpty(id)
        some <- findBy(id)
      } yield some).get
    }
  } yield somes

みたいな。

これで良い感じ。

Option.getは悪だけどTry.getは良い

Option.getをすると、NullPointerになるから、getとか絶対使うなよ!!って言うけど、だからって、Try.getもだぞ!!ってことにならないと考えてます。

Try {
  // something
} match {
  case Success(s) => s
  case Failure(e) => throw e
}

って、

Try {
  // something
}.get

と同じなんだよね。

結構、前者のコードを見る。(まあ、Scala初級者なだけですが…)

けど、そんなん冗長なことせんと、そこは素直にgetでいこーぜって思う。

まとめ

ざっと、眠たい目をこすりながら、思い出したのは、以上。

他に思い出したら追記かなんかします。

以上です。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした