スポンサーサイト

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

F#入門初級編第1回(Active Pattern (1))

今回のお題は「Active Pattern (1)」です。
 
Active Patternとは、パターンマッチで直接マッチングを行うのでなく、「引数が一つの関数」で一加工して、その結果をマッチングを行うという、補助関数変換付きパターンマッチです。(原材料を関数で加工してからマッチングをおこなうという訳です。)いくつかパターンがあるので順に取り上げていきます。
 
(1)Single-case active pattern
 
これは、ただ一種類の「引数が一つの関数」を適用してからその結果をマッチングするパターンです。
適用する関数は次のように定義します。
 
let (|ident|) = expr (ただしidentの先頭は大文字で始まること)
 
それでは、ただ一つの関数として、7で割った余りを返す関数を採用してみます。
 
let (|Mod7|)x = 
    x % 7
    
これを利用して、引数に対し7で割った余りが1か3であればture,それ以外の時はfalseを返す関数を定義してみます。
 
let isMod7Eq1or3 k =
    match k with
    |Mod7  1           //#1
    |Mod7  3
        -> true
    | _ 
        -> false
 
#1の部分でMod7という関数をスコープ内で探し、あればそれをkに適用し、その結果を1とマッチさせてみるというしくみです。
 
なお部分適用もできて、例えば次のようになります。
(部分適用後、引数の数が1つになることが肝要です。)
 
let (|MoreThan|) k x =
    x > k
 
let test00 n =
    match n with
    |MoreThan 3 true            //#1
                -> printfn "3より大"   
    |MoreThan 0 true 
                -> printfn "0より大で3以下"
    |_ 
                -> printfn "0以下"
 
ここでは#1の部分でMoreThanという関数をスコープ内で探し、「3を部分適用した関数」をnに適用してそれの結果がtrueにマッチするかどうかを試しています。(MoreThan 3)n がtrueとマッチしているかどうかを調べている訳でMoreThanの二つの引数を与えているわけではありません。
 
実行例
 
> test00  8;;
3より大
val it : unit = ()
 
それでは、これを用いて有名なFizz-Buzz問題を解いてみます。これは次のようなものです。
1から100までの数をプリントするプログラムを書け。ただし3の倍数のときは数の代わりに「Fizz」と、5の倍数のときは「Buzz」とプリントし、3と5両方の倍数の場合には「FizzBuzz」とプリントすること。
http://www.aoky.net/articles/jeff_atwood/why_cant_programmers_program.htmより)
 
コードは次のようになります。
 
let (|MyMod|) k x = 
    x % k
 
let intToFizzBuzzStr n =
    match n with
    |MyMod 15 0 -> "FizzBuss" //#1
    |MyMod  3 0 -> "Fizz"
    |MyMod  5 0 -> "Buzz"
    | _         -> n.ToString()
         
[1 .. 100] 
|> List.map intToFizzBuzzStr 
|> List.iter (printfn "%s")
 
            
例えば#1の部分では(|MyMod|) k xに15を部分適用した関数すなわち15で割った余りを返す関数をnに適用してそれが0とマッチ(一致)するかどうかを試しています。
 
(2)Partial Active Patterns
 
これは一つのActive Pattern用に複数の関数を準備して、これでどうだ、これでどうだ、という感じで次々とトライしていくパターンです。
適用する関数は次のように定義します。
 
let (|ident|_|) = expr (ただしidentの先頭は大文字で始まること)
 
また返す型はoption<'a>型であることが必要です.(Some ('a)またはNone)
この関数で処理できる場合は「Someなんとか」を返し、処理し切れない場合はNoneを返し、他の関数に処理を委ねます。
 
それでは「世界のなべあつ問題」をこれを用いてやってみます。
これは「3の倍数と3が付く数字の時にアホ」になるというものです。
 
let AhoStr = "アホ"
 
//文字列として3を含む時、処理成功としてSome(AhoStr)を返す
let (|Incl3|_|) x =
    if x.ToString().Contains("3") then
        Some(AhoStr)
    else
        None
 
//3で割り切れるとき、処理成功としてAhoStrを返す
let (|CanDiv3|_|) x =
    if x % 3 = 0 then
        Some(AhoStr) //#1
    else
        None
 
let intToAhoStr n =               //#2
    match n with
    |Incl3 s    -> s              //#3
    |CanDiv3 s  -> s              //#4
    |_          -> n.ToString()
 
[1 .. 100] 
|> List.map intToAhoStr
|> List.iter (printfn "%s")
 
例えば#2でnが15の時は、まず#3で(|Incl3|_) 15が評価され、結果としてNoneが返されます。Noneが返されたときは、無条件でマッチ失敗と見なされるので、次の#4が試されます。(CanDiv3|_) 15は#1の部分でSome(AhoStr)を返します。ここで#4で結果がsとマッチするのですが、注意する点はSome(AhoStr)がマッチするのではなくて、「Someの中身」がマッチするということです。(そういう仕様になっているのです。)ということで#4でsにAhoStrが束縛され、これが返り値となります。 
 
引数が2個以上の処理関数を準備して、これを部分適用して複数の「引数1個の処理関数」を作り出して、これを用いるという手法もよくとられます。
 
例えば先ほどのFizz-Buzz問題は次のようにも解けます。
 
let (|Mul|_|) x y = 
    if y % x = 0 then 
        Some(y / x) //商を返す(この値は今回は利用しない)
    else None;;
 
let fizzbuzz n= 
    match n with
    | Mul 15 _ -> "FizzBuzz" //マッチ部分は捨てる(以下も同様)
    | Mul  3 _ -> "Fizz"
    | Mul  5 _ -> "Buzz"
    | n -> n.ToString()
 
[1 .. 100] 
|> List.map intToFizzBuzzStr 
|> List.iter (printfn "%s")
 
このコードは
http://rainyday.blog.so-net.ne.jp/2007-05-25および
http://igeta.cocolog-nifty.com/blog/2008/04/active_patterns.htmlを参考にさせて頂きました。
 
 
このような手法は正規表現がらみのものをよく見かけます。興味がある方は、例えば下などを参照してみてください。
http://www.infoq.com/articles/Beyond-Foundations-FSharp(一番最後の例です。)
 
さて、ここで注意点ですが、アクティブパターンにつかう関数は1対1と多対1の二つのパターンがあるということです。例えば割って余りだけを返す関数は、多対1です。(余りが2というだけでは、元の数は分からない。)このようなアクティブパターンは、考えなしに使うとバグが出かねないので、ライブラリ化等をして一般的(広域)的に使うアクティブパターンについては、その辺の点をよく考慮にいれるべきです。
それと、アクティブパターンは関数を適用して、それからマッチに持ち込むわけですから、関数適用のコストがかかるということも念頭において使用すべきです。
(この辺のことはいげ太さんに、教えていただきました。)
スポンサーサイト

テーマ : プログラミング
ジャンル : コンピュータ

コメントの投稿

非公開コメント

No title

> test00 1 8;;
は、
> test00 8;;
の間違いだと思います。

あと、

let (|CanDivide3|Include3|Other|) n = if n % 3 = 0 then CanDivide3 elif n.ToString().Contains("3") then Include3 else Other
let intToAhoStr n =
match n with
|Include3
|CanDivide3 -> "アホ"
|_ -> n.ToString()
のようにも書けますよ。

No title

すいません、またつまらないミスをしていました。訂正しました。このような場合は、パターンは、つなげた方が分かりやすいですね。

No title

パターンをまとめるというだけじゃなくて、
>また返す型はoption<'a>型であることが必要です.(Some ('a)またはNone)
オプション型でなくてもいいということです。

No title

すいません。Multi-case Active Patternを使っていたのですね。注意不足でした。

管理人のみ閲覧できます

このコメントは管理人のみ閲覧できます
プロフィール

T GYOUTEN

Author:T GYOUTEN
F#と英単語とフリーソフトと読書に興味があります。
ホームページでフリーソフトも公開しています。どぞ御贔屓に。

最新記事
最新コメント
最新トラックバック
月別アーカイブ
カテゴリ
フリーエリア
フリーエリア
blogram投票ボタン
検索フォーム
RSSリンクの表示
リンク
ブロとも申請フォーム

この人とブロともになる

QRコード
QRコード
上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。