Scalaの抽出子(apply, unapply)サンプルと呼ばれるタイミング

More than 1 year has passed since last update.


apply, unapply

ScalaのObject内で良くセットで定義されるメソッド、

apply (注入、injection)、unapply(抽出、extraction)。

細かい事はコップ本26章やその他の有用な記事を見るとして...

unapplyメソッドを持つオブジェクトは抽出子(extractorObject)と呼ばれる。


例1


例1

object extractorObject {

// 抽出子オブジェクト
object Sample {

def apply(str: String): String = {
// 文字列を作った
str + " (applyが呼ばれた)"
}

// ここでの引数strはapplyが作り出した文字列
def unapply(str: String): Option[String] = {
if (str.nonEmpty) Some(str + (" (unapplyも呼ばれた)")) else None
}
}

def main(args: Array[String]): Unit = {
val p = Sample("本日は晴天なり") // Sample#applyを呼ぶ
println(p)

p match {
case Sample(s) => println(s) // Sample#unapplyを呼ぶ
case _ => println("No match")
}
}
}

// 本日は晴天なり (applyが呼ばれた)
// 本日は晴天なり (applyが呼ばれた) (unapplyも呼ばれた)


例1では次のような順序で、apply unapply が実行される。


  • mainメソッドで Sample("任意の文字列") を生成する



    • applyが呼ばれる



    • applyは任意の文字列を受け取って、「任意の文字列 (applyが呼ばれた)」という文字列を作る。



  • パターンマッチが抽出子オブジェクトを参照するパターンを検出する



    • unapplyが呼ばれる



      • Sample.unapply(str) が実は呼び出されている




    • unapplyapply構築プロセスを逆戻すような働きをし、引数に「任意の文字列 + (applyが呼ばれた)」という文字列を受取り、「任意の文字列 (applyが呼ばれた) (unapplyも呼ばれた)」という文字列を返す




例2

例2は、木についてる葉っぱの数を数えるもの。とある木(treeA)に、枝(Branch)があり葉っぱ(Leaf)が数枚ついている。参考


例2

object extractorObject {

abstract class Tree

// 枝クラス
class Branch(val left: Tree, val right: Tree) extends Tree

// 枝オブジェクト
object Branch {

def apply(left: Tree, right: Tree): Tree = {
println("Branch#apply 呼ばれたよ")
new Branch(left, right)
}

def unapply(x: Tree): Option[(Tree, Tree)] = {
println("Branch#unapply 呼ばれたよ")
x match {
case y: Branch => Some(y.left, y.right)
case _ => None
}
}
}

// 葉クラス
class Leaf(val x: Int) extends Tree

// 葉オブジェクト
object Leaf {

def apply(x: Int): Tree = {
println("Leaf#apply 呼ばれたよ" + "x = " + x.toString)
new Leaf(x)
}

def unapply(x: Tree): Option[Int] = {
println("Leaf#unapply 呼ばれたよ")
x match {
case y: Leaf => Some(y.x)
case _ => None
}
}
}

// 木A
val treeA = Branch(Branch(Leaf(1), Leaf(2)), Branch(Leaf(3), Leaf(4)))

def sumLeaves(t: Tree): Int = t match {
case Branch(l, r) => sumLeaves(l) + sumLeaves(r)
case Leaf(x) => x
}

// 実行
def main(args: Array[String]): Unit = {
println("sum of leaves = " + sumLeaves(treeA))
}
}

// 結果

// Leaf#apply 呼ばれたよ x = 1
// Leaf#apply 呼ばれたよ x = 2
// Branch#apply 呼ばれたよ
// Leaf#apply 呼ばれたよ x = 3
// Leaf#apply 呼ばれたよ x = 4
// Branch#apply 呼ばれたよ
// Branch#apply 呼ばれたよ

// Branch#unapply 呼ばれたよ
// Branch#unapply 呼ばれたよ
// Branch#unapply 呼ばれたよ
// Leaf#unapply 呼ばれたよ
// Branch#unapply 呼ばれたよ
// Leaf#unapply 呼ばれたよ
// Branch#unapply 呼ばれたよ
// Branch#unapply 呼ばれたよ
// Leaf#unapply 呼ばれたよ
// Branch#unapply 呼ばれたよ
// Leaf#unapply 呼ばれたよ

// sum of leaves = 10


例2では



  • val treeA = Branch(Branch(Leaf(1), Leaf(2)), Branch(Leaf(3), Leaf(4)))



    • treeA に枝葉のオブジェクトが作成される

    • 作成順に従って、applyが呼ばれているのが分かる




  • sumLeaves(treeA)



    • case Branch(l, r) のマッチングが行われる


    • Branch.unapply(Branch)が呼ばれる


    • Some(Branch, Branch)が返り、sumLeavescase Branch(l, r)l,rにそれぞれ束縛される




  • sumLeaves(treeA)



    • case Leaf(x)のマッチングが行われる


    • Leaf.unapply(Leaf)が呼ばれる


    • Some(Int) が返り、case Leaf(x)xに束縛される



  • Leafの1 + 2 + 3 + 4 = 10が計算される

なお、枝クラス枝オブジェクト葉クラス葉オブジェクトをすべて取っ払って代わりに、

  case class Branch(left: Tree, right: Tree) extends Tree

case class Leaf(x: Int) extends Tree

としても、ちゃんと動く。

applyはなんとなく分かるのですが、unapplyの挙動がイマイチ分からなかったので調べてみました:kissing: