LoginSignup
44
40

More than 5 years have passed since last update.

第22章:Scalaの抽出子

Last updated at Posted at 2014-03-31

今回は 抽出子 だよ。

こいつを使えるようになると、更にパターンマッチが便利になるんだ。

こんな感じで進めてみるよ!

抽出子とは

unapplyメソッドが定義されているオブジェクト のことです。
定義としては簡単だね!

このメソッドの役割は、受け取ったインスタンスを分解して、 何かを抽出する ことです。
パターンマッチでこのメソッドが使われることが多いです。

抽出子を使う

早速使ってみよう!

パターンマッチで使う

User.scala
import scala.Option

class User(private val firstname: String, private val lastname: String)

object User {

  def unapply(user: User) = Option((user.firstname, user.lastname))

  def main(args: Array[String]): Unit = {
    val user = new User("剛次", "赤石")

    user match {
      case User(firstname, lastname) => println(lastname + " " + firstname)
      case _ => println("Not user.")
    }
  }

}

実行してみよう!

$ scalac User.scala
$ scala  scala User
赤石 剛次

説明

まずUserクラスを用意してみたぞ。

class User(private val firstname: String, private val lastname: String)

次にUserクラスのコンパニオンオブジェクトを作り、この中にunapplyメソッドを用意した。

object User {

  def unapply(user: User) = Option((user.firstname, user.lastname))
}

unapplyメソッドの決め事は、以下みたいなんだ。

  • 引数は、抽出対象のインスタンス
  • 戻り値は、抽出したモノをOptionで包む

今回引数は、コンパニオンクラスのインスタンスにしてみた。
そして戻り値は、fristnamelastnameにした。

最後に使う場所をUserオブジェクトのmainメソッドとして用意した。

  def main(args: Array[String]): Unit = {
    val user = new User("剛次", "赤石")

    user match {
      case User(firstname, lastname) => println(lastname + " " + firstname)
      case _ => println("Not user.")
    }
  }

生成したUserインスタンスのuserをパターンマッチすると、case User(firstname, lastname)User#unapplyが呼び出され、
抽出した値を変数firstnamelastnameに束縛する。

こんな感じで 抽出子 を使うことができるんだ。

代入で使う

こんな形でも使えるぜ!さっきのUserクラスがいる前提でだからな!

object User1 {

  def main(args: Array[String]): Unit = {
    val user = new User("剛次", "赤石")
    val User(firstname, lastname) = user

    println(lastname + " " + firstname)
  }

}

先にパターンマッチで説明しちゃったけど、こっちの方が抽出子って感じがする。

抽出子って使いようによっては可能性が広がるね!

unapplySeqメソッド

unapplyにSeqが付いてるメソッド。ということは、もしかして。。。
そうです。可変長のオブジェクトからの抽出子となります。

Seqがついているのが気になったので、ScaladocでSetMapを見てみた。やっぱりunapplyunapplySeqがいない。
つまり順番で抽出することがわかるね。

あとはさらっと流すよ。

val seq = Seq(1, 2, 3, 4)
val Seq(x, y , _*) = seq

抽出して変数x, yに束縛したね。

applyメソッド

基本編ではapplyメソッドについて触れていなかったので、ここで触れます。

まず以下のようなapplyメソッドを持つオブジェクトを用意する。

scala> object ApplyTest { def apply(message: String) { println(message) } }

このメソッドを呼び出すには、こうするよね?

scala> ApplyTest.apply("乱坊少佐")
乱坊少佐

でもApplyTest("乱坊少佐")とするだけでも、applyメソッドを呼びすことが可能なんだ!

scala> ApplyTest("乱坊少佐")
乱坊少佐

いわゆる、シンタックスシュガー(糖衣構文)ってやつだね。

こういう使い方は少ないのかな?ファクトリーメソッド的な感じで使うことが多いみたいなんだ。
例えばこんな感じ。

scala> class ApplyClass(val message: String); object ApplyClass { def apply(message: String): ApplyClass = { new ApplyClass(message) } }
defined class ApplyClass
defined module ApplyClass

scala> ApplyClass("乱坊少佐")
res0: ApplyClass = ApplyClass@43679ffd

まぁシンタックスシュガーということだけで、後は他のメソッドと変わらないからな!

ところで、今までもよく使っているオブジェクトがありました。それはなんでしょうか?

そうです。コレクション系です。軽くSeqオブジェクトapplyメソッドについて見てみよう。

def apply[A](elems: A*): Seq[A]

Seq[A]を返している。つまり今まで

Seq(1, 2, 3)

なんて書いていたのは、applyメソッドを使っていたのです。

更にちなみに、引数のelems: A*は複数を表す。もちろん0個の場合もOK。
正規表現の 直前の文字の0回以上の繰り返し と似ているね。

ケースクラス

apply, unapply共通な話なので、ここに記載する。
ケースクラスを作ると、apply, unapplyメソッドが自動で実装されます。興味がある人は、見てみてね!

applyとunapplyの関係

関係ありそうだよね?
まず、常に両方を定義する必要はありません。必要な方だけ定義すれば良いです。
それは今までの例を見ればわかってもらえると思います。

両方を定義する場合はどうだろう?
実は 注入抽出 の関係が存在している。

  • 注入(injection)
    • applyメソッド
  • 抽出(extraction)
    • unapplyメソッド

と考えるんだ。
抽出は 何か を抽出し、注入は 何か を注入する。 何か を共通の存在にできるね。

必ずこの関係性を満たす必要はないけど、なるべく満たした方が良いなんてコップ本に書いてあった。
何か を共通のモノにできない場合は、設計を疑った方が良いのかもね。

まとめ

抽出子は使いこなすと強力な武器になりそうだろ?
ぜひ使ってみてね!

今回も
体で感じてくれたかな?

44
40
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
44
40