Fight the Future

Java言語とJVM、そしてJavaエコシステム全般にまつわること

ScalaのAdvanced Exampleを写経する(6)-extractor

ScalaのAdvanced Exampleを写経する(5)-case class - Fight the Future じゅくのblogを変更したソース。
extractorPatterns.scala | The Scala Programming Language


パターンマッチングはケースクラス使わなくてもobjectでもできる。
ただしapplyメソッドとunapplyメソッドを定義する必要がある。

package sample.snippet

object ExtractorPatterns {
  
  /** Here, er replaced case classes of Patterns.scala
   * with objects that hide the actual implementation of Blanch and Leaf.
   * Note that the remaining code does not change.
   * In this way, we can change the implementation lator without affecting clients,
   * which is called representation independence.
   */
  abstract class Tree
  
  object Branch {
    /* method to contruct brancheds @see ExtractorPatterns.tree1 */
    def apply(left: Tree, right: Tree): Tree = {
      println("Branch#apply is called.")
      new BranchImpl(left, right)
    }
    /* extractor method referenced in match expressions @see RxtractorPatterns.sumLeave */
    def unapply(x: Tree): Option[(Tree, Tree)] ={
      println("Brach#unapply is called.")      
	    x match {
	      case y:BranchImpl => Some(y.left, y.right)
	      case _ => None
	    }
    } 
    private class BranchImpl(val left: Tree, val right: Tree) extends Tree
  }
  
  object Leaf {
    def apply(x: Int): Tree = {
      println("Leaf#apply is called. x = " + x)
      new LeafImpl(x)
    }
    
    def unapply(x: Tree): Option[Int] = {
      println("Leaf#unapply is called.")
      x match {
        case y: LeafImpl => Some(y.x)
        case _ => None
      }
    }
    private class LeafImpl(val x: Int) extends Tree
  }
 
  /** Here, the task of the case class condtructor is perfomed by the 
   * method Branch.apply - the singleton Branch is treated as if it 
   * were a function value. This trick words with any value that
   * has apply method.
   */
  val tree1 = Branch(Branch(Leaf(1), Leaf(2)), Branch(Leaf(3), Leaf(4)))
  
  /** For extractors, it is not the name of a case class, but the name of
   * the singleton object Branch which is used to refer to its extractor method
   * Branch.unapply - the pattern is the 'reverse' of a method
   * call, with the result being matched in the subpatterns. This works
   * for any value that has an appropriate extractor method.
   */
  def sumLeaves(t: Tree): Int = t match {
    case Branch(l, r) => sumLeaves(l) + sumLeaves(r)
    case Leaf(x) => x
  }
  
  def find[A, B](it: Iterator[Pair[A, B]], x: A): Option[B] = {
    var result: Option[B] = None
    while(it.hasNext && result == None) {
      val Pair(x1, y) = it.next;
      if(x == x1) result = Some(y)
    }
    result
  }
  
  def printFinds[A](xs: List[Pair[A, String]], x: A) = 
    find(xs.elements, x) match {
      case Some(y) => println(y)
      case None => println("no match")
    }
  
  def main(args: Array[String]) {
    println("main starts.")
    println("sum of leafs = " + sumLeaves(tree1))
    val l = List(Pair(3, "three"), Pair(4, "four"))
    printFinds(l, 4)
    printFinds(l, 10)
  }
}

objectはBranchやLeafの実装クラスを隠してる。
クライアントのコードはケースクラスのときとまったく同じコードで変更の必要がない。
この方法ならクライアントに影響を与えること無く実装を変更できる。
これは「表現独立」と呼ばれる。


ケースクラスのコンストラクタの代わりにapplyメソッドが実行される。
シングルトンであるBranchはまるで関数の値のように扱われる。
このトリックはapplyメソッドを持つすべての値に適用される。


extractor(抽出器)により、ケースクラスの名前ではなくシングルトンオブジェクトであるBranchの名前を指定する。
Branchは抽出メソッドunpplyを参照するために使われる。
このパターンはメソッド呼び出しの「逆」である。
サブパターンにおいてマッチした結果となる。
じれが適切な抽出メソッドがあるすべての値に適用される。


実行結果。

Leaf#apply is called. x = 1
Leaf#apply is called. x = 2
Branch#apply is called.
Leaf#apply is called. x = 3
Leaf#apply is called. x = 4
Branch#apply is called.
Branch#apply is called.
main starts.
Brach#unapply is called.
Brach#unapply is called.
Brach#unapply is called.
Leaf#unapply is called.
Brach#unapply is called.
Leaf#unapply is called.
Brach#unapply is called.
Brach#unapply is called.
Leaf#unapply is called.
Brach#unapply is called.
Leaf#unapply is called.
sum of leafs = 10
four
no match

コンストラクタを呼び出したときにapplyメソッドが、マッチするところでunapplyメソッドが呼び出されてる。
unapplyはパターンマッチングのたびに呼び出されるため、たとえばtがLeafである場合まずBranchのunapplyを呼び出し次にLeafのunapplyを呼び出す。上から順に比較するため。