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)
が実は呼び出されている
-
-
unapply
はapply
の構築プロセスを逆戻すような働きをし、引数に「任意の文字列 + (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)
が返り、sumLeaves
のcase 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
の挙動がイマイチ分からなかったので調べてみました