memo.txt

教えていただいたこと、勉強したことのメモです。

「match式だけがパターンマッチだと思うなよ」

この記事は

初めてのアドベントカレンダー

F# Advent Calendar 2013 - connpass 12日目の記事です。
昨日は @k_dispose さんの「F# - SqlEntityConnectionTypeProviderについて - Qiita [キータ]」でした。
明日は id:kyon_mm さんの「F#のなんかすごい話」です。

「パターンマッチを教えてもらった」くらいの内容です

挑発的なタイトルで恐縮です。

「パターンマッチと言えばmatch式」みたいになってるけど、違う!
F#では変数を導入する場所ではどこでもパターンマッチできる!

とおっしゃる先輩方に、パターンマッチを教えてもらった備忘録です。
教えていただいたことを、検証しながらまとめました。

パターンマッチとは

(概ね)「単方向の単一化」です。
あるパターンに何かマッチさせること。

MSDN

パターン マッチ (F#)

「パターンは、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式以外でも便利に使える