「match式だけがパターンマッチだと思うなよ」
この記事は
初めてのアドベントカレンダー
F# Advent Calendar 2013 - connpass 12日目の記事です。
昨日は @k_dispose さんの「F# - SqlEntityConnectionTypeProviderについて - Qiita [キータ]」でした。
明日は id:kyon_mm さんの「F#のなんかすごい話」です。
「パターンマッチを教えてもらった」くらいの内容です
挑発的なタイトルで恐縮です。
「パターンマッチと言えばmatch式」みたいになってるけど、違う! F#では変数を導入する場所ではどこでもパターンマッチできる!
とおっしゃる先輩方に、パターンマッチを教えてもらった備忘録です。
教えていただいたことを、検証しながらまとめました。
パターンマッチとは
(概ね)「単方向の単一化」です。
あるパターンに何かマッチさせること。
MSDN
「パターンは、match 式などの多くの言語構成要素で使用されます。let 束縛、ラムダ式、および try...with 式に関連付けられている例外ハンドラーで関数の引数を処理する場合に使用されます。」
このようにMSDNさんも「match式だけじゃないよ」っておっしゃっています。
変数もパターンマッチ
let x = 3 (* val x : int *)
これもパターンマッチ(変数パターン)。
「変数パターン x に 3 がマッチした」と言える。
変数が導入できるところでは、必ずパターンマッチが使える。
変数を別のパターンに置き換えてみた
let x = 3 の x
let 3 = 3 (* 定数パターン *)
(警告は出るけど)書けました。
let 3 = 4
こう書くとMatchFailureExceptionになります。
let f x = 3 の x
let f (true|false) = 3 (* val f : bool -> int (ORパターン) *)
書けました。
let f x = x の x
type Hoge = | A of string | B of string * int let f (A x | B (x, _)) = x (* val f : Hoge -> string *) (* 識別子パターン・変数パターン・ワイルドカードパターン・ORパターン *)
書けました。
ところで
「これ、どうやって使うんですか。」
「できるよっていうだけ。」
「なるほど。」
「こういうのなら便利だし、よく使うよ!」
(match式以外の)書けて便利なパターンマッチ
レコードを扱う
type Person = { Name : string * string Age : int } let p = { Name = ("NAKAJIMA", "Rika"); Age = 26} let { Name = n; Age = a } = p
p が { Name = n; Age = a }このパターンにマッチする。
val p : Person = {Name = ("NAKAJIMA", "Rika"); Age = 26;} val n : string * string = ("NAKAJIMA", "Rika") val a : int = 26
タプルを扱う
//レコードのコードに↓を追加 let (familyName, lastName) = p.Name
(familyName, lastName)このパターンにp.Nameがマッチする。
val lastName : string = "Rika" val familyName : string = "NAKAJIMA"
レコードとタプルの例はまとめられるよね
type Person = { Name : string * string Age : int } let p = { Name = ("NAKAJIMA", "Rika"); Age = 26} let { Name = (familyName, lastName); Age = a } = p
レコードとタプルの例はこのようにまとめられる。
「パターンはネストすることができるから、便利ですね。」
val p : Person = {Name = ("NAKAJIMA", "Rika"); Age = 26;} val lastName : string = "Rika" val familyName : string = "NAKAJIMA" val a : int = 26
リストを扱う
let (_ :: tail | ([] as tail) ) = [1; 2; 3]
val tail : int list = [2; 3]
一応こっちも。
let (_ :: tail | ([] as tail) ) = [1]
val tail : int list = []
match a with x -> ... の x を扱う
let a b = match b with |([1, 2, 3] | [1, 2, 4]) -> true (* ORパターン *) |other -> false
val a : b:(int * int * int) list -> bool
ちなみに、このORパターン部分は以下のようにも書けます。
let a b = match b with |[1, 2, 3] (* この行頭の | はmatch式の構文 *) |[1, 2, 4] -> true (* この行頭の | はORの | *) |other -> false
さらにORパターンをネストさせてこうも書けます。
let a b = match b with |[1, 2, (3 | 4)] -> true |other -> false
すっきり。
リテラルにもマッチできます
let hoge = 3 match 4 with | hoge -> true | _ -> false
こう書くと1行目のhogeと3行目のhogeは別のhogeになります。
match式内のhogeは変数パターンになり、必ずhogeにマッチしてしまいます。
_ に対して「This rule will never be matched」という警告も出ます。
val hoge : int = 3 val it : bool = true
この3がシステム的に重要な場合など、直接定数を書くとDRYに反する。
そんなときは↓こう書きます。
[<Literal>] let Hoge = 3 match 4 with | Hoge -> true (* Hogeは大文字始まり *) | _ -> false
ちゃんと4にマッチしないためfalseという結果になりました。
val Hoge : int = 3 val it : bool = false
まとめ
- パターンマッチはあるパターンに対して何かをマッチさせること
- 変数が使えるところではどこでもパターンマッチを使える
- match式以外でも便利に使える