かずきのBlog@hatena

すきな言語は C# + XAML の組み合わせ。Azure Functions も好き。最近は Go 言語勉強中。日本マイクロソフトで働いていますが、ここに書いていることは個人的なメモなので会社の公式見解ではありません。

手軽なスクリプト言語としてのF# その23「アクティブパターン」

早いもので23回目になりました。
今回はアクティブパターンというものを紹介してみようと思います。こいつも使いこなすとなかなか便利そうなので、ちょっと本気だして習得しようと思いました。


さて、アクティブパターンとはなんぞや?ということですが、一言でいうと入力を仕分けしてくれる関数みたいなものだと理解しました。
ある値に対して仕分けを行うというとmatchが、そのものずばりな動きをしてくれるものだと思うのですが、このアクティブパターンはmatch式で使ったりします。

とりあえず、定義の仕方を見てみようと思います。定義は以下のようになります。

// Complete active patternと言われてるもの
let (|識別子1|識別子2| ... |) 引数 = 式

// Partial active patternと言われてるもの
let (|識別子1|識別子2| ... |_|) 引数 = 式

関数の定義の関数名のところが、(| |)で囲まれて|で区切られています。因みに(| ... |)のことをバナナクリップと呼ぶらしいです。雑学でした。
これは式の部分で、識別子として定義したものを戻り値とすることで入力に対して、それがどんな値かということをラベルづけするようなイメージです。Partial active patternのほうは、Option型を使って戻り値を返すのが特徴で仕分けしきれなかったケースをNoneで表します。

では、とりあえず単純な例として0〜33をLow、 34〜66をMiddle、66〜をHightと仕分けするアクティブパターンを定義してみようと思います。

// 入力を3パターンに仕分けする
let (|Low|Middle|Hight|) x =
    match x with
    | x when x < 33 -> Low
    | x when x < 66 -> Middle
    | _ -> Hight

何気にはじめて使う構文を使ってますが、match式のwhenの後に条件式を書くと、その条件式がtrueの時に右側の式が評価されるという動きをするものです。まぁif文と似たような雰囲気ですね。こんな風にバナナクリップの中で定義した値を入力値に対して返します。
では、実際に使ってみようと思います。今回はLowの時に「うわぁ小さい・・・」、Middleの時に「ふ〜ん普通ね」、Hightの時に「うわっおっき〜い」と表示する関数を作ってみます。

let printState x =
    match x with
    | Low -> printfn "うわぁ小さい・・・"
    | Middle -> printfn "ふ〜ん普通ね"
    | Hight -> printfn "うわっおっき〜い"

// 使ってみる
printState -1
printState 0
printState 23
printState 50
printState 100
printState 200

実行すると以下のような結果になります。

うわぁ小さい・・・
うわぁ小さい・・・
うわぁ小さい・・・
ふ〜ん普通ね
うわっおっき〜い
うわっおっき〜い

このように、すっきりとパターンマッチが書けるようになります。素敵ですね。

次に、Partial active patternのほうを見てみます。これは、仕分けしきれないものをNoneで返すというところ以外はComplete active patternと同じです。実際に定義してみます。今回は0より小さくて100より大きいものは範囲外としてみました。

let (|OutOfRange|_|) x = 
    if x > 100 || x < 0 then Some OutOfRange
    else None

今回はSome OutOfRangeという戻り値を返していますが、ここは「Some 任意の値」を返すことも出来ます。このPartial active patternはpartialという名前が示す通り複数のアクティブパターンと組み合わせて使うことが出来ます。試しに、さっきのやつと組み合わせて使ってみようと思います。0〜100の範囲に入らないものは「えっ・・・規格外すぎる」と表示してみようと思います。

let printState x =
    match x with
    // このように他のアクティブパターンと組み合わせて使える
    | OutOfRange -> printfn "えっ・・・規格外すぎる"
    // OutOfRangeに当てはまらないと下に流れていく
    | Low -> printfn "うわぁ小さい・・・"
    | Middle -> printfn "ふ〜ん普通ね"
    | Hight -> printfn "うわっおっき〜い"

// 使ってみる
printState -1
printState 0
printState 23
printState 50
printState 100
printState 200

実行してみましょう。

えっ・・・規格外すぎる
うわぁ小さい・・・
うわぁ小さい・・・
ふ〜ん普通ね
うわっおっき〜い
えっ・・・規格外すぎる

ばっちり0より小さいものと100より大きいものが規格外と表示されてます。このようにちょっと複雑な条件になりがちなパターンマッチを部品化して組み合わせて使えるという点でアクティブパターンは夢ひろがると思います。


あとは、入力値された値をばらすのにも使えたりします。

open System

// DateTime型を年月日にばらすアクティブパターン
let (|Date|) (d : DateTime) = (d.Year, d.Month, d.Day)

// 適当な日付を作って
let date = DateTime.ParseExact("2011/02/11", "yyyy/MM/dd", null)

// パターンマッチで値をばらせる
match date with
|Date (y, m, d) -> printfn "match %d %d %d" y m d

// 関数チックにもつかえる
let y, m, d = (|Date|) date
printfn "関数チックにばらす %d %d %d" y m d

実行結果

match 2011 2 11
関数チックにばらす 2011 2 11

なかなか、使いでのよさそうな奴だと思いました。因みにF#に無くて不満だった正規表現のパターンマッチですがMSDNのサンプルにそのものズバリの答えが書いてありました。
MSDNのサンプルの劣化版ですが、簡単にしたものを以下に示します。

ポイントは2つ以上の引数を持つアクティブパターンは、1つの引数を受け取るアクティブパターンになるように引数をバインドして使うという所です。今回の例だとRegexは2つの引数を受け取るので1つ引数を指定して使っています。そして、パターンマッチでひっかかった結果が変数sに入るという寸法です。

open System.Text.RegularExpressions

// patternにマッチした部分を返す
let (|Regex|_|) pattern str =
    let r = Regex.Match(str, pattern)
    if r.Success then Some r.Value
    else None

let printMatch input =
    match input with
    | Regex "太郎" s -> printfn "マッチしたのは %s" s
    | Regex ".の." s -> printfn "マッチしたのは %s" s
    | _ -> printfn "マッチしなかった"

printMatch "田中 太郎がいけめん"
printMatch "あれはてたこうやでのデスマッチ"
printMatch "田中 一郎が消えた"

実行すると、以下のようになります。正規表現でひっかかった場所だけが表示されてるのがわかります。

マッチしたのは 太郎
マッチしたのは でのデ
マッチしなかった

以上、アクティブパターンでした。

過去記事