アクティブパターン
「match式だけがパターンマッチだと思うなよ」 - memo.txt
本当はこの記事に入るといいねって言われていたアクティブパターンについて。
アクティブ パターンでは、入力データを分割する名前付きパーティションを定義できます。これによって、判別共用体の場合と同様に、パターン マッチ式でそれらの名前を使用できます。アクティブ パターンを使用すると、パーティションごとにカスタマイズした方法でデータを分解できます。
アクティブパターンは関数
定義する
- (|、|)をバナナクリップと呼ぶ。
- let (|パターン識別子|) 引数 = パターン識別子(戻り値)の書式で定義する。
- パターン識別子は大文字始まり。
- (戻り値)部分は任意。
- パターン識別子が1つの場合はlet (|パターン識別子|) 引数 = 戻り値でも定義できる。
let (|Upper|)(str:string) = Upper(str.ToUpper())
引数の文字列を大文字にするstring -> string の関数です。
match式の分岐で使う
- match x with のxを暗黙的に引数にする。
- アクティブパターンの戻り値とパターンを比較する。
let f (str:string) = match str with | Upper "HOGE" -> "HOGE" | Upper "PIYO" -> "PIYO" | x -> "other"
Upperの引数にstrが渡され、大文字になったstrとパターンを比較します。
f "hoge" (* "HOGE" *) f "HOGE" (* "HOGE" *) f "Hoge" (* "HOGE" *) f "pIyO" (* "PIYO" *) f "Foo" (* "other" *) f "" (* "other" *)
パラメータ化されたアクティブパターン
- 最後以外の引数は前から順に部分適用される。
- 最後の引数はmatch x with のxを暗黙的に使う。
let (|Reg|) (pattern:string) (str:string) = Regex.IsMatch(str, pattern) let regex (str:string) = match str with | Reg "^.{2}$" true -> "2文字" | Reg "^.{3}$" true -> "3文字" | _ ->"not match" regex "" (* "not match" *) regex "a" (* "not match" *) regex "aa" (* "2文字" *) regex "aaa" (* "3文字" *) regex "aaaa" (* "not match" *)
パーシャルアクティブパターン
- バナナクリップの中にパターン識別子と_(アンダースコア)を|で区切って書く。
- Opotion<'T>の戻り値がないとエラー。
- 戻り値がSomeの場合、直接値が取れる。
let (|Multiple|_|) (m:int) (n:int) = if n <> 0 && n % m = 0 then Some(n.ToString()) else None let fizzbuzz(n:int) = match n with | n when n < 1 -> "illegal input : " + n.ToString() | Multiple 15 x -> "FizzBuzz : " + x | Multiple 3 x -> "Fizz : " + x | Multiple 5 x -> "Buzz : " + x | _ -> n.ToString() fizzbuzz 3 (* "Fizz : 3" *) fizzbuzz 5 (* "Buzz : 5" *) fizzbuzz 15 (* "FizzBuzz : 15" *) fizzbuzz 1 (* "1" *) fizzbuzz 0 (* "illegal input : 0" *) fizzbuzz -1 (* "illegal input : -1" *)
複数アクティブパターン
- バナナクリップの中に|で区切って複数のパターン識別子を書く。(7つまで)
- すべてのパターン識別子が返されるように定義しないとエラー。
- すべてのパターン識別子にmatchさせないと警告。
- パターン識別子毎に別の戻り値の型を定義できる。
let (|Name|Age|) (input:string) = if Regex.IsMatch(input, "^[0-9]{1,3}$") then Age(int input) else Name(input) let profileCheck(input:string) = match input with |Name x -> x |Age x -> if 10 < x && x < 20 then "target" else "not target" profileCheck "Ann" (* "Ann" *) profileCheck "15" (* "target" *) profileCheck "10" (* "not target" *)
let (|Folder|File|) (input:string) = let attr = File.GetAttributes(input) if attr.HasFlag(FileAttributes.Directory) then Folder else File let pathCheck (input:string) = match input with |File -> "File" |Folder -> "Folder" pathCheck @"c:\" (* "Folder" *) pathCheck @"c:\temp" (* "Folder" *) pathCheck @"c:\temp\MyTest.txt" (* "File" *)
種類と使い分け
1要素のアクティブパターン
- 入力データに名前を付ける。
- すべての入力データに対して行う処理を隠蔽するのに使う。
- 利用者は必ず成功する処理しか書けない。
パーシャルアクティブパターン
- 入力データをある条件に当てはまるデータと、それ以外のデータに分け、当てはまるデータのみに名前を付ける。
- 「それ以外」のデータに興味がない場合に使う。
複数アクティブパターン
- 入力データを分別して、それぞれに名前を付ける。
- データをすべて分別したい場合に使う。
- 利用する際に、使われていないパターンがあると警告が出るので、実装漏れを検知しやすい。