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の挙動がイマイチ分からなかったので調べてみました![]()