かずきのBlog@hatena

日本マイクロソフトに勤めています。XAML + C#の組み合わせをメインに、たまにASP.NETやJavaなどの.NET系以外のことも書いています。掲載内容は個人の見解であり、所属する企業を代表するものではありません。

手軽なスクリプト言語としてのF# その20「パターンマッチ」

さて、関数型言語には大体ある(と思ってる)パターンマッチについてやってみます。といっても私が経験してる関数型言語HaskellとF#だけなので、全部の言語にあるかどうかは良く知りません。まぁC#でパターンマッチに該当するものって何?っていうとswitch文がそれにあたります。ただし、C#のswitch文がお粗末でかわいそうに見えてくるくらい強力です。

書き方は以下のようになります。

match 評価したい式 with
| パターン -> 結果式
| パターン -> 結果式
| パターン -> 結果式
| パターン -> 結果式

というわけで簡単な例を書いてみます。とりあえず普通のswitch文相当の簡単なものから。

let f x =
    // xの値で結果がを変えるswitch文みたいなもの
    match x with
    | 0 -> "Zero"
    | 1 -> "One"
    | 2 -> "Two"
    | _ -> "Many"

// 実行してみよう
printfn "f 0 = %s" <| f 0
printfn "f 1 = %s" <| f 1
printfn "f 2 = %s" <| f 2
printfn "f 3 = %s" <| f 3

上記のプログラムでは0 1 2 _がパターンマッチの所にあたります。このように数字を指定するのは、定数パターンと呼ばれています。数字以外にも"hoge"のような文字列や1.0などのような実数、Color.Redのような値も使えます。
上記の実行結果は以下のようになります。

f 0 = Zero
f 1 = One
f 2 = Two
f 3 = Many

定数だけだと、C#とかのswitchと大差ありません。ちょっと前に使ったoption型を使ってみようと思います。option型はSomeとNoneを持つので、それをパターンのところに書きます。早速プログラム例を見てみましょう。

let f x =
    // Someだったら値を取得してNoneだったら0を返す
    match x with
    | Some v -> v
    | None -> 0


printfn "%A" <| f (Some 10)
printfn "%A" <| f None

こうやってoption型の結果を受け取ったらmatchでやるのがスマートだと思われます。
その他にタプルもパターンマッチ出来ます。

let f x =
    match x with
    | (0, 0) -> "Zero"
    | (1, 0) -> "One"
    | (0, _) -> "Hoge"
    | _ -> "Other"

printfn "%s" <| f (0, 0)
printfn "%s" <| f (1, 0)
printfn "%s" <| f (0, 100)
printfn "%s" <| f (2, 2)

実行結果は以下のようになります。_はパターンマッチの何にでもマッチする便利な子なんです。

Zero
One
Hoge
Other

他には、パターンマッチにorやandといった条件も指定できます。

// orの例
let f x =
    match x with
    | 1 | 2 | 3 -> "小さい"
    | 4 | 5 | 6 -> "普通"
    | 7 | 8 | 9 -> "大きい"
    | _ -> "その他"

printfn "%s" <| f -1
printfn "%s" <| f 1
printfn "%s" <| f 5
printfn "%s" <| f 9

andの場合は&で指定したりします。上記のプログラムを実行すると以下のような結果になります。

その他
小さい
普通
大きい

今更FizzBuzz

ちょっと前に、Twitterigetaさんが示してくれた例でパターンマッチの強力さを思い知ったのでコードを紹介したいと思います。こういう風に使えるとは…と思いました。

let fb x =
    match (x%3, x%5) with
    |(0, 0) -> "FizzBuzz"
    |(0, _) -> "Fizz"
    |(_, 0) -> "Buzz"
    | _ -> string x

[1..100] |>
    List.map fb |>
    List.iter (printf "%s ")

実行結果

1 2 Fizz 4 Buzz Fizz 7 8 Fizz Buzz 11 Fizz 13 14 FizzBuzz 16 17 Fizz 19 Buzz Fiz
z 22 23 Fizz Buzz 26 Fizz 28 29 FizzBuzz 31 32 Fizz 34 Buzz Fizz 37 38 Fizz Buzz
 41 Fizz 43 44 FizzBuzz 46 47 Fizz 49 Buzz Fizz 52 53 Fizz Buzz 56 Fizz 58 59 Fi
zzBuzz 61 62 Fizz 64 Buzz Fizz 67 68 Fizz Buzz 71 Fizz 73 74 FizzBuzz 76 77 Fizz
 79 Buzz Fizz 82 83 Fizz Buzz 86 Fizz 88 89 FizzBuzz 91 92 Fizz 94 Buzz Fizz 97
98 Fizz Buzz

なかなか、3で割った余りと5で割った余りのタプルでパターンマッチするという発想は出てきませんでした。ということでパターンマッチについては以上です!