連載
» 2012年04月20日 00時00分 公開

スケーラブルで関数型でオブジェクト指向なScala入門(5):Scalaのクラスとオブジェクト、パターンマッチ (3/3)

[中村修太,クラスメソッド株式会社]
前のページへ 1|2|3       

オブジェクトのパターンマッチと抽出子「unapply」メソッド

 連載第4回の「基本的なパターンマッチとScalaで重要な“関数”」では、複数の選択肢から1つを選ばせるパターンマッチを紹介しました。そのときは文字列や数値など、基本的なパターンマッチの紹介でしたが、オブジェクトに対してもパターンマッチができます。

「Option」型とは

 オブジェクトのパターンマッチを紹介する前に、「Option」クラスと、そのサブクラスである「Some」クラスと「None」クラスについて説明します。

 Javaの場合、オブジェクトの参照先がない場合には変数に「null」を代入します(Rubyの場合は「nil」)。nullはオブジェクトではないので、nullに対してメソッドを呼び出そうとすると例外が発生します。

 Scalaでも、nullは使えますが、変数や関数の戻り値が値を参照しない可能性がある場合、「Option」型の使用を推奨しています。値が存在しない場合は「None」を返し、存在する場合は「Some」で実際の値をラップして返すようにします。

 OptionはScalaコレクションでもよく使用されています。例えば、「Map」の「get」メソッドはキーに対応する値が見つかったときにSome(値)を返し、キーが見つからなければNoneを返します。

scala> val m = Map("Scala"->1,"Java"->2)
m: scala.collection.immutable.Map[java.lang.String,Int] = Map(Scala -> 1, Java -> 2)
 
scala> m.get("Scala")
res3: Option[Int] = Some(1)
 
scala> m.get("Ruby")
res4: Option[Int] = None

  Option型から実際の値を得るには、「get」メソッドを使います。OptionがSome型の場合には実際の値が返り、None型の場合は例外が発生します。「getOrElse」メソッドを使用すれば、None型の場合に指定した値を返せます(※MapクラスもgetOrElseメソッドを持っているので注意)。

scala> m.get("Scala").get
res6: Int = 1
 
scala> m.get("Ruby").get
java.util.NoSuchElementException: None.get
……
 
scala> m.get("Ruby").getOrElse("nothing!")
res8: Any = nothing!

 Option型はパターンマッチと併せて使うときに効果を発揮します。戻ってきた値がSomeかNoneかで処理を簡単に分岐できます。

scala> def testMatch(opt:Option[Int]) = {
     |   opt match {
     |     case Some(n) => println(n)
     |     case None => println("None!")
     |   }
     | }
 
scala> testMatch(Some(10))
10
 
scala> testMatch(None)
None!

 Some型は「caseクラス」(※後述)なので、変数にバインドして取り出せます。Optionとパターンマッチを組み合わせた記述は非常によく使います。

「unapply」を使用したパターンマッチ

 Option型を使用したパターンマッチでは、オブジェクトを「case」部分に指定していました。もちろん、自分で定義したクラスもcase部分に指定できます。ただし、そのままではパターンマッチの対象になりません。

 「unapply」はインスタンス構成要素を抽出するためのメソッドで、「抽出子」とも呼びます。このメソッド定義することで、オブジェクトをcase部分に指定できます。

 2つのオブジェクトを定義してパターンマッチを試してみましょう。

object Apple {
    def unapply(a: Any): Boolean = {
        if (a.isInstanceOf[Apple]) true else false
    }
}
   
class Orange(val name:String)
object Orange {
    def apply(name: String): Orange = new Orange(name)
    def unapply(a: Orange):Option[String] = Some(a.name)
}

  「Apple」は「match」で指定された値の型をチェックし、「Apple」型であればマッチしたと見なします(※isInstanceOfで型のチェックが可能)。「Orange」は引数をSomeでラップして「name」フィールドを返します。

 次に、渡された値をmatchで検証する関数を作成します。Apple型もしくはOrange型かつnameフィールドが「DEKOPON」の場合にマッチするようになっています。

def matchTest(value:Any) = {
  value match {
    case Apple => println("Apple")
    case Orange("DEKOPON") => println("Orange.name=DEKOPON")
    case _ => println("other")
  }
}

 オブジェクトに「unapply」(もしくは「unapplySeq」)というメソッドを定義した状態で、caseにそのオブジェクトを書くと、unapply(もしくは「unapplySeq」)が呼ばれます。

scala> matchTest(Orange("something"))
other
 
scala> matchTest(Orange("DEKOPON"))
Orange.name=DEKOPON
 
scala> matchTest(Apple)
Apple
 
scala> matchTest("hello")
other

 渡されたオブジェクトの型やフィールドの値によって処理が分岐されています。

 このように、unapplyメソッドを使用すればオブジェクトの要素をマッチできます。

 また、Orangeに対するcase部分を下記のようにすると、nameフィールドの値を変数にバインドして使用できます。

    case Orange(name) => println("Orange.name=" + name)

「caseクラス」でオブクジェクトを簡単にパターンマッチ

 先ほどはオブジェクトにunapplyを定義してパターンマッチで使いました。しかしScalaでは、オブジェクトを簡単にパターンマッチで使うための方法があります。そのための手段とは、caseクラスを使用することです。

 「caseクラス」とは、あらかじめ想定されるデフォルト処理を定義した状態でクラスを作成するための仕組みです。先ほどの例でも使用したAppleとOrangeを、あらためてcaseクラスとして定義してみましょう。caseクラスを定義するには、classの前に「case」と記述するだけです。

scala> case class Apple(name:String)
defined class Apple
 
scala> case class Orange(name:String)
defined class Orange

 caseクラスには下記のような特徴があります。

  • applyが定義されるので、newを使用せずにインスタンス化可能
  • caseクラスのコンストラクタ引数は、すべてvalとして扱われる
  • toStringやequals、copyなどのメソッドが提供される
  • unapplyが定義されるので、パターンマッチで使用することが可能

 では、定義したcaseクラスを使用してみましょう。applyが自動的に定義されるため、newを使わずにインスタンス化できます。

scala> val apple = Apple("fuji")
apple: Apple = Apple(fuji)

 自動でvalとして定義されたnameフィールドを参照できます。

scala> apple.name
res10: String = fuji

 toStringも適切に定義されています。

scala> apple.toString
res11: String = Apple(fuji)

 copyメソッドを使用すれば、既存のオブジェクトを基に新しいオブジェクトを定義できます。

scala> val newApple = apple.copy(name = "jonagold")
newApple: Apple = Apple(jonagold)

 パターンマッチ用の関数を再度定義します。

def matchTest(value:Any) = {
    value match {
        case Apple(name) => println("Apple.name=" + name)
        case Orange("DEKOPON") => println("Orange")
        case _ => println("other")
    }
}

 「case Apple(name)」となっているcase部分は、valueがApple型であればnameがどんな値でもマッチし、nameフィールドがcase部分のname変数にバインドされます。「case Orange("DEKOPON")"」では、先ほどと同じくvalueがOrange型かつnameフィールドが「DEKOPON」の場合にマッチします。

 caseクラスとするだけで、クラスにいろいろな機能が追加されているのが分かります。

クラスとオブジェクトは、今後の基本

 さて、今回はクラスとオブジェクトの基本的な使用方法や、コンパニオンオブジェクトという特殊なシングルトンオブジェクト、Optionの使い方、オブジェクトでパターンマッチする方法を紹介しましたが、いかがでしたでしょうか。

 本稿での内容は、次回以降の基礎になる内容なので、覚えておいてください。次回はオブジェクトの「継承」を紹介する予定です。

筆者紹介

クラスメソッド株式会社

中村修太(なかむら しゅうた)

クラスメソッド勤務の新しもの好きプログラマーです。昨年、東京から山口県に引っ越し、現在はノマドワーカーとして働いています。好きなJazzを聴きながらプログラミングするのが大好きです。



前のページへ 1|2|3       

Copyright © ITmedia, Inc. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。