mapやforeachだけじゃなくてfor式も使おう
ScalaにはOption型というものが存在します。今回はこのOptionとfor式の使い方についてです。
Optionの使い方については前の記事を参照してください。初心者向けですが大体ポイントはおさえているかと思います。
背景:matchのネスト
(自分含め)Scala初心者は喜んでOptionとmatchを使うのですが、そのうちあることに気づき始めます。
「matchのネストがやばいんだけど・・・」
これを何とかする為にはfor式という選択肢が効果的かと思います。
サンプルコード(だめな例)
3階層くらいネストしているentityオブジェクトがあるとして、これの一番深い部分にある値を取得して出力したいとします。
case class User(
name: String,
company: Option[Company]
)
case class Company(
name: String,
category: Option[Category]
)
case class Category(
id: Long,
name: Option[String]
)
object Main extends App {
val user: Option[User] = Some(User("name", Some(Company("company", Some(Category(1, Some("category")))))))
user match {
case Some(x) => x.company match {
case Some(x) => x.category match {
case Some(x) => x.name match {
case Some(x) => println(x)
case None =>
}
case None =>
}
case None =>
}
case None =>
}
}
うは!きつい・・・!
ユーザが存在しており、そのユーザの会社が存在しており、その会社のカテゴリー(業種)が存在しており、そのカテゴリーの名前が存在しているならば出力。やばい。
Scala中級者、上級者の人でも一度は見たことありますよね?自分の昔のコードだったりレビューでだったり・・・。ちなみに自分はこういうの書いたことありますし、レビューでOK出したこともあります。。。
for式で回避
for式は一見forと付いているので「リスト用か・・・」と思いがちですが、Optionにも使えるので使いましょう。
object Main extends App {
val user: Option[User] = Some(User("name", Some(Company("company", Some(Category(1, Some("category")))))))
for {
u <- user
c <- u.company
c2 <- c.category
categoryName <- c2.name
} println(categoryName)
}
ちなみにこう書いてもOKです。
user.flatMap(_.company).flatMap(_.category).flatMap(_.name).foreach(println)
forもflatMapも同じようなもんなので、どっちでも良いですが、一番初めの例よりかははるかに見やすいですね!
for式の罠
上記にも書きましたが、まず「for」と言っているのでループ用という固定観念が付いて回って、なかなか今回の利用例にたどり着けません。実際自分の周りの同僚は同じような感じでした。自分も後で気づいた感じです。
for式はScalaを学んだ初めの方に触れましたが、mapやforeachの存在を知って完全に使わなくなりました・・・。
なので、もしこれを読んだScala初心者は「for式はベテランでも使うテクニック」と覚えておいた方が良いと思います。
そして残念ながら今回の例だとforの途中で失敗したケースに対して何かを行う、ということができません。
例えばu <- userの部分で失敗したらthrow new A()するけど、c <- u.companyで失敗したらthrow new B()する、みたいな。これに対するベストな解は見つかっていませんが、以下とか参考になるかもしれません。
ScalaでWebアプリケーションのエラー処理を綺麗に書く - はこべブログ
てか、書いててこっちの方が良い記事な気がしてきました。
Scalaでmatchがネストしていくのをなんとかしたい小ネタ
とにかくfor式を使って良いScalaライフを送りましょう!