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")