mapやforeachだけじゃなくてfor式も使おう


ScalaにはOption型というものが存在します。今回はこのOptionとfor式の使い方についてです。

Optionの使い方については前の記事を参照してください。初心者向けですが大体ポイントはおさえているかと思います。

Scala初心者向け 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ライフを送りましょう!