Scalaのapplyとunapplyについて調べてみた

applyとunapplyについて調べてみたのでメモ代わりにブログに書きます。
今回はコップ本の26章抽出子を参考に調べました。

applyメソッド
→使う目的としては値を構築するために使われる

unapplyメソッド
→使う目的としては値をマッチングして分解すること

scala> object Email {
     |   def apply(user:String, domain:String) = user + "@" + domain
     |   def unapply(str: String): Option[(String, String)] = {
     |     val parts = str split "@"
     |     if (parts.length == 2) Some(parts(0), parts(1)) else None
     |   }
     | }
defined object Email

//2つの値から1つのメールアドレスを作成
scala> Email( "hikonori", "sample.com")
res1: String = hikonori@sample.com

//メールアドレスから@の左右を分割して2つの文字列に
scala> Email.unapply("hikonori@sample.com")
res2: Option[(String, String)] = Some((hikonori,sample.com))

Emailオブジェクトの中では、
applyメソッドを注入(injection)といいます。
複数の引数をとりそれを基に1組の要素を与えるため

unapplyメソッドは抽出といいます。
→1組の要素を引数としてそれを分解した要素を抽出するため

抽出子オブジェクトについて

抽出子オブジェクトとはメンバーの1つとしてunapplyというメソッドを持っているオブジェクトです。unapplyメソッドの目的はさっきも説明したとおり、値をマッチングして分解することです。
また抽出子オブジェクトは値を構築するための相補的なapplyメソッドを定義していることが多いですが、必須ではありません。

下の例がunapplyメソッドだけでapplyメソッドをもたない抽出子オブジェクトです。
下の抽出子オブジェクトではそもそも値を構築する必要がないため、applyメソッドは必要ありません。

scala> object UpperCase {
     | def unapply(s: String): Boolean = s.toUpperCase == s
     | }
defined object UpperCase

scala> UpperCase.unapply("scala")
res1: Boolean = false

scala> UpperCase.unapply("SCALA")
res2: Boolean = true

scala> UpperCase.unapply("Scala")
res3: Boolean = false
val userForm = Form(
  mapping(
    "name" -> text,
    "age" -> number
  )(UserData.apply)(UserData.unapply)
)

フォーム定義のとこでよく見る↑の表記は公式サイトではこんな風に説明されていました。
https://www.playframework.com/documentation/2.3.x/ScalaForms
→mapping メソッドには好きな関数を渡すことができます。ケースクラスを構築または分解する場合、デフォルトの apply と unapply 関数がまさしくケースクラスの構築・分解を行う関数なので、それらを渡しておけば問題ありません。

ということで今回はapply、unapplyをみていきました。
これから実際に使うことがあればユースケースなど紹介できればと思います。

追記
unapplyメソッドが定義されている場合のパターンマッチについて

//caseクラスではunapplyが定義される
scala> case class Phone( color: String, price: Int)
defined class Phone

scala> val iPhone6 = Phone( "gold", 650 )
iPhone6: Phone = Phone(gold,650)

//unapplyメソッドが定義されていることで値の参照が可能
scala> iPhone6 match{
     | case Phone(color, price) => println( "iPhone6は" + color + "で" + price + "です")
     | case _ =>
     | }
iPhone6はgoldで650です

>|scala|
//通常のクラスではunapplyメソッドは自動で定義されない
scala> class Laptop( val color: String, val price: Int)
defined class Laptop

scala> val macBookPro = new Laptop("silver", 1500)
macBookPro: Laptop = Laptop@2f465398

//ケースクラスのように値の参照できない
scala> macBookPro match{
     | case Laptop( color, price) => println( "MacbookProは" + color + "で" + price + "です")
     | case _ =>
     | }
<console>:11: error: not found: value Laptop
              case Laptop( color, price) => println( "MacbookProは" + color + "で" + price + "です")
                   ^

//通常のクラスでもコンパニオンオブジェクトでunapplyを定義すると
//ケースクラス同様、値の参照ができます
scala> class Laptop( val color: String, val price: Int)
defined class Laptop

scala> object Laptop {
     | def unapply( l: Laptop):Option[(String, Int)] = Some(( l.color, l.price ))
     | }
defined object Laptop

scala> val macBookPro = new Laptop("silver", 1500)
macBookPro: Laptop = Laptop@69a3d1d

scala> macBookPro match{
     | case Laptop( color, price) => println( "MacbookProは" + color + "で" + price + "です")
     | case _ =>
     | }
MacbookProはsilverで1500です

ケースクラスではunapplyメソッドが自動で生成されるので、パターンマッチで値の参照ができました。
しかし通常のクラスではunapplyメソッドは定義されないため、パターンマッチで値の参照を行いたい場合別途unapplyメソッドを定義する必要があります。