アクティブパターンでFizzBuzz

アクティブパターンを使いこせるとかっこいいですよね。アクティブパターンでFizzBuzzは目新しい話ではなさそうですが、やってみました。

アクティブパターンを使わない例。

まずは普通に。fizzbuzz関数の中で条件分岐してます。

[<Test>]
let ``fizzbuzz without Active Pattern`` () =
let fizzbuzz = function
| n when n % 15 = 0 -> "fizzbuzz"
| n when n % 3 = 0 -> "fizz"
| n when n % 5 = 0 -> "buzz"
| n -> string n

seq {1 .. 100}
|> Seq.map fizzbuzz
|> Seq.iter (printfn "%s")

とりあえずアクティブパターンを使ってみた例。

FizzBuzzがアクティブパターン。この例だとありがたみがないです。
Seq.mapへのラムダ関数でアクティブパターンの結果を取得していますが、この記述方法はちょっとわかりにくい。慣れの問題だと思いますが。xにFizzBuzzの戻り値がバインディングされます。

[<Test>]
let ``fizzbuzz with Active Pattern`` () =
let (|FizzBuzz|) = function
| n when n % 15 = 0 -> "fizzbuzz"
| n when n % 3 = 0 -> "fizz"
| n when n % 5 = 0 -> "buzz"
| n -> string n

seq {1 .. 100}
|> Seq.map (fun (FizzBuzz x) -> x)
|> Seq.iter (printfn "%s")

0で除算するコードの重複を排除する形でアクティブパターンを使用した例。

F# の Active Pattern (で FizzBuzz) [OCaml]を参考にさせてもらいました。アクティブパターンへの引数がどれで戻り値がどれ?というのがわかりにくいです。慣れるしかないって感じでしょうか。この例だと、fizzbuzz関数への引数がMulのdividendになって、Mulを呼び出しているときに15や3や5を渡していますがこれがMulのmodulusになります。そして結果のSomeの要素が、_ に戻されます。

[<Test>]
let ``fizzbuzz with Partial and Parameterized Active Pattern`` () =
let (|Mul|_|) modulus dividend =
if dividend % modulus = 0 then Some(dividend / modulus) else None

let fizzbuzz = function
| Mul 15 _ -> "fizzbuzz"
| Mul 3 _ -> "fizz"
| Mul 5 _ -> "buzz"
| n -> string n

seq {1 .. 100}
|> Seq.map fizzbuzz
|> Seq.iter (printfn "%s")


本題とはずれますが、functionキーワードを使うべきか迷います。今回は使えるところは全部つかってみました。
たとえば、最初のケースのfizzbuzz関数ですが、こう書いても同じなんですよね。

let fizzbuzz n = 
if n % 15 = 0 then "fizzbuzz"
elif n % 3 = 0 then "fizz"
elif n % 5 = 0 then "buzz"
else string n