読者です 読者をやめる 読者になる 読者になる

算譜王におれはなる!!!!

偏りはあると思うけど情報技術全般についてマイペースに書くよ。

ことりんのパターンマッチング

※下記URLのサイトを参考にしました。英語、または技術的な知識が至らず、内容に誤りが含まれるおそれがありますので、ご了承ください。
※本エントリは参考サイトの翻訳をベースに、加筆・変更を施した構成となっています。
※サンプルコードは参考サイトから引用しています。

参考サイト http://confluence.jetbrains.net/display/Kotlin/Pattern+matching

パターンマッチングは、オブジェクトが特定の構造を持っているかどうかをチェックすることを私たちに可能ならしめます。例えば、二分木のノードが両方とも子を持っているとか、子を持たない左の子を持っているとか。

パターンマッチングの演算子(isと!is)

パターンマッチングは、パターンマッチに成功した場合にtrueを、それ以外はfalseを返すis演算子によって行われます。その否定形は!isです。パターンマッチングの最もシンプルな次の形は、オブジェクトが与えられた型に従うかどうかをチェックします(Javainstanceofチェックに似ている)。

//is
if (obj is String) {
  print(obj.length)
}
// !is
if (obj !is String) {
  print("Not a String")
}
else {
  print(obj.length)
}

上記のとおり、型チェックした後のブロックでキャストが行われていません。これについてはスマートキャスト(smart cast)をご覧ください。

is!isは、when式の分岐条件として使用される場合もあります。

when (x) {
  is Int -> print(x)
  is List<Int> -> print(x.sum())
  !is Number -> print("Not even a number")
  else -> print("can't do anything")
}

when式についてはこちらをご覧ください。

パターン

is!isの右側のパターンは、型、定数、タプルの構造とその他のオブジェクトに対するマッチを許可し、そしてマッチしたオブジェクトをサブパターンによって変数へバインドします。
パターンの文法をご覧ください。

注意:複雑なパターンは実装されていません。
今のところ、is/!isの後に型を置くことしかできません。これに対応するイシューをご覧ください。

タプルのパターン、定数とワイルドカードのパターン

型以外のものに対するマッチングもできます。パターンの1つの便利な種類は、タプルパターンです。次のコードは xの型が(Int, String)であるかをチェックしていることに注意してください。

x is #(Int, String)

しかし、タプルコンポーネントの型以外のこともできます。

x is #(1, String)

これは左のコンポーネントが1で、かつ右のコンポーネントが任意のString値であるタプルとマッチします。パターンとして任意の定数リテラルを使用できます。そして、ネストタプルも使用できます。

x is #("a", #(1.0, (false, Any?)))

パターンにもNULL許容型が生じる可能性があります。例えば Any?は任意のオブジェクトまたは nullとマッチします。つまりなんでもマッチします。Kotlinはこのための速記を持ちます。ワイルドカードパターンと呼ばれ、* と書きます。

x is #(*, *, *)

これは、どの3コンポーネントのタプルともマッチします。

バインディングパターン

マッチングしたものの一部をキャプチャする必要がしょっちゅうあります。これがバインディングパターン(binding pattern)がある理由です。バインディングパターンは基本的にパターン内部に変数を宣言します。例えば「xが2コンポーネントのタプルだった場合、aとbと名付けたい」という場合は

if (x is #(val a, val b)) {
  print(a)
  print(b)
}

簡単なバインディングパターンのval aは、なんにでもマッチし、それをaに割り当てます。さらに、それは

  • パターン:val a is #(*, *)
  • レンジ:val a in 1..10
  • 静的な型(めったに使用しない):val a : Boolean

によって制限することができます。

バインディングパターンのwhen式について考えてください。

when (some_complex_expression) {
  is #(val a, val b is String) -> print("$a and ${b.length}")
  is #(val a is Int, val b is Int) -> print(a + b)
  is #(*, val b in 1..100) -> print(b)
}
分解者パターン

タプルは、パターンマッチングできる唯一の構造体ではありません。通常のオブジェクトのために、Kotlinは分解者パターン(decomposer pattern)を提供します。例えば、二分木構造に対してマッチングしたいとき、それをこのような方法で使用できます。

when (xml) {
  is Tree#(*, null) -> print("no right child")
  is Tree#(val l is Tree, val r is Tree) -> print("two children: $l and $r")
  is Tree -> print("just a tree")
}

分解者パターンはenumとすべてのパラメータがvalの主要コンストラクタを宣言しているクラスでは、デフォルトで有効です。例えばTreeクラスはこのように定義することができます。

class Tree(val left : Tree?, val right : Tree?)

実際、これはコンパイラに分解者関数を自動生成させます。自分でそのような関数を宣言することもできます。

分解者関数

分解者関数(decomposer function)は、パターンマッチしたオブジェクト(x is patternの場合のx)上で呼び出される関数で、それは分解者の名前の後に提供されているタプルかnullとマッチするようなタプルを返さなければなりません。先のTreeの例において、#の前に来ているTreeはクラス名ではなく、分解者関数名です。この(コンパイラによって自動生成された)関数はこのようになります。

decomposer fun Any?.Tree() : #(Tree?, Tree?)? {
  if (this is Tree)
    return #(this.left, this.right)
  return null
}

decomposerアノテーションは、パターンマッチングの外側でこの関数を呼び出してはならないことをコンパイラへ知らせることに注意してください。そうでない場合、Treeのコンストラクタと混同してしまうでしょう。

一般に、分解者はマッチング対象者に適用できる関数とみなされます。すなわち、それは任意の名前を持つことができ、メンバ関数になることができます。

class Date(val timestamp : Long) {
  fun mmddyy() : #(Int, Int, Int)? = #(month, day, year)

  // ...
}

fun demo(d : Date) {
  when (d) {
    is mmddyy#(05, 13, val a) -> print("May 13th of $a"))
    // ...
  }
}

または拡張関数で

fun Date.mmddyy() : #(Int, Int, Int)? = #(month, day, year)