アクティブパターン
アクティブパターンは、特定の物に複数の見え方を提供するために使用することができます。
構文は次の通りです。
参考
- アクティブパターンの構文
-
// Active pattern of one choice.
let (|identifier|) [arguments] valueToMatch = expression
// Active Pattern with multiple choices.
// Uses a FSharp.Core.Choice<_,...,_> based on the number of case names. In F#, the limitation n <= 7 applies.
let (|identifier1|identifier2|...|) valueToMatch = expression
// Partial active pattern definition.
// Uses a FSharp.Core.option<_> to represent if the type is satisfied at the call site.
let (|identifier|_|) [arguments] valueToMatch = expression
例えば、整数は2の倍数とそれ以外にもわけられますし
正の数と負の数という風にもわけられます。
これをプログラムすると次のようになります。
上の構文では、2番目の構文を使っています
ODDやEVENなどの識別子は一度に最大7つまで使うことができます
アクティブパターンを使用した整数のパターンマッチ
let (|ODD|EVEN|) (x:int) =
if x%2=0 then EVEN else ODD;;
let (|POSITIVE|NEGATIVE|) (x:int) =
if x>=0 then POSITIVE else NEGATIVE;;
match 10 with
| POSITIVE -> printfn "positive"
| NEGATIVE -> printfn "negative";;
match 10 with
| ODD -> printfn "odd"
| EVEN -> printfn "even";;
同じint型の変数に対して
異なった観点でのパターンマッチが出来ていることに
注意してみてください。
(|と|)の記号はバナナクリップと呼ばれ、
この型のletバインドによって作成される関数はアクティブ認識エンジンと呼ばれます。
デザインガイドが推奨している使い方
上で見たとおり、アクティブパターンは複数の方法で
同じ対象に対するパターンマッチを行えます。
これをある種のカプセル化のように利用する事が出来ます。
F#のデザインガイドのドラフト(2010 august版)(pdf注意)の3.6 Union Typesによると
一般的なガイドとして
Discriminated Unionをユーザに公開するのは避けるべきだそうです。
そこで、アクティブパターンの出番です。
内部的な実装はDiscriminated Unionや他のデータ型を使い
ユーザに公開するインタフェースをアクティブパターンで提供する
という使い方が出来ます。
そのような設計をしておくことで
内部実装を変更したとしても、アクティブパターンでその差を吸収出来るため
ユーザコードに影響なくライブラリの変更がしやすくなります。
このテクニックは、F#でのMonadic parser combinator実装である
FParsecにも使われています。
部分的なアクティブパターン
三番目の構文は部分的なアクティブパターンと呼ばれるものを定義します。
これは、入力の一部だけにマッチするアクティブパターンを定義することに相当します。
組のない単純な宝くじを例に考えてみます。
一等が123412番、その前後の数値が前後賞で二等から四等は省略して五等が下一桁が2だとします。
この場合、一等かつ五等という結果がありえます。(多分普通の宝くじはそうなっていませんが)
普通の宝くじの場合は重なりがないので通常のアクティブパターンで表現できますが
部分的に結果が重なるようなケースでは部分的なアクティブパターンを使うとうまくいきます。
アクティブパターンを使用した整数のパターンマッチ
let ittou_number = 123412
let gotou_number = 2
let (|ITTOU|_|) x = if x = ittou_number then Some(x) else None
let (|ZENGOSHOU|_|) x = if (x = ittou_number-1) || (x = ittou_number+1) then Some(x) else None
let (|GOTOU|_|) x = if x%10 = gotou_number then Some(x) else None
let kuji = function
| ITTOU(x) & GOTOU(y) -> printfn "一等かつ五等 : %d" x
| ITTOU(x) -> printfn "一等 : %d" x
| ZENGOSHOU(x) -> printfn "前後賞 : %d" x
| GOTOU(x) -> printfn "五等 : %d" x
| _ -> printfn "はずれ"
kuji ittou_number
kuji (ittou_number+1)
kuji (ittou_number-1)
kuji 100002
kuji 100000