scala: unapplyを覚えたのでリファクタリングしてみる

match caseが読みにくいぞ

やりたいことは、「プレミアムユーザか否か、と年齢によって処理を分ける」です。 case class Userを用いて書かれた下記コードがあるとします。

package advanced.functional

case class User(
                    userId: Long,
                    premium: Boolean,
                    age: Int
                  )


object Test extends App {
  val meg = new User(1L, true, 20)
  val jho = new User(2L, true, 10)

  private def printUserType(user: User) = 
    user match {
      case User(_, true, age) if age >= 18 => println(s"adult premium user")
      case User(_, true, _) => println(s"child premium user")
      case _ =>  println(s"user")
    }

  printUserType(meg)  // adult premium user
  printUserType(jho)  // child premium user
  printUserType(beth) // user

}

match内が可読性が低く、また同じ条件を他のコードで使いまわせないのでリファクタリングしたいですね。

Let's リファクタリング1

case classでは自動でunapply()を適応してくれてるので、ただのclassにしてunapply()を自分で書きなおしてみます。

//case class User(
//                    userId: Long,
//                    premium: Boolean,
//                    age: Int
//                  )

class User(
               val  userId: Long,
               val  premium: Boolean,
               val  age: Int
               )

object User {
  // case classで自動で適応されているunapplyは下記のはず
  def unapply(u: User): Option[(Long, Boolean, Int)] = Some((u.userId,u.premium,u.age))
}

object User のunapplyの中に、match caseで書いていた条件を持っていきましょう。

object User {
  def unapply(u: User): Option[(Boolean, Boolean)]  = Some((u.premium, isAdultUser(u.age)))
  def isAdultUser(age: Int):Boolean = age >= 18
}

object Test extends App {
  val meg = new User(1L, true, 20)
  val jo = new User(2L, true, 10)
  val beth = new User(3L, false, 5)

  private def printUserType(user: User) =
    user match {
      case User(true, true) =>  println(s"adult premium user")
      case User(true, false) =>  println(s"child premium user")
      case _ =>  println(s"user")
    }

  printUserType(meg)  // adult premium user
  printUserType(jho)  // child premium user
  printUserType(beth) // user
}

matchからuserIdの_と、if文が消えてすっきりはしました。が、まだまだベストな気はしません。User(true, true)て何やねんとなります。

Let's リファクタリング2

isAdultUser()というunapplyを作ってはどうでしょう? せっかく書き換えたcase classも元に戻します。

case class User(
                    userId: Long,
                    premium: Boolean,
                    age: Int
                  )

object isAdultUser {
  def unapply(age: Int):Boolean = age >= 18
}

object Test extends App {
  val meg =  User(1L, true, 20)
  val jo =  User(2L, true, 10)
  val beth =  User(3L, false, 5)

  private def printUserType(user: User) =
    (user.premium, user.age) match {
      case (true, isAdultUser()) => println(s"adult premium user")
      case (true, _) => println(s"child premium user")
      case _ =>  println(s"user")
    }

  printUserType(meg)
  printUserType(jo)
  printUserType(beth)
}

おぉ、わかりやすくなった。

      case (true, _) => println(s"child premium user")

は下記ようにはかけないのですね。ここは勉強が足りない。

      case (true,  !isAdultUser()) => println(s"child premium user")
      case (true,  isAdultUser() == false) => println(s"child premium user")