F# でコマンドレットを書いてる pt.37
krymtkts/pocof ではあまり Active Patterns を使ってなかった。 特に理由があって避けてたわけじゃなく、わたしの F# 力が低いのもあるけどなんか大げさな感じがして使ってなかった。
でもこの辺を読んでもっと気軽に使っても良さそうやな~という気になってきた。
そこで可読性高めるのと汎用化を期待して使いはじめてみた。 Partial Active Patterns やら Complete Active Patterns やらあるけど、気に入ってるのは Single-case Active Pattern だ。 値を変換するパターンがこれに相当する。
こんな感じでタプルを照準に並び替えて返すものを作ってみたが、良さそう。
let (|Ascending|) (x, y) = if x < y then (x, y) else (y, x)
pocof は範囲選択の実装の都合上、数値を並び替える場面がいくつかあった。 あるいは 2 つの数値のうち小さい方、大きい方を取るとか。 そういったケースにこれがバッチリハマる。
並び替えるだけならこう。
match x, y with Ascending xy -> xy
大小どちらかを取る場合、例えば小さい方はこう。
match x, y with Ascending (x, _) -> x
既に 2 値のタプルがあるなら、こうするのがシンプルか。
xy |> function Ascending xy -> xy
この状態だと単に関数呼び出しでしかないけど、他のパターンと組み合わせていい塩梅にできる。
let xy = (0, -1)
match xy with
| x, y when x < 0 || y < 0 -> None
| Ascending xy -> Some(xy)
いいやん。 ボトムアッププログラミングが捗るな(ゆーても既にモノ出来てるからボトムアップしようがないけど)。
あとは多少複雑なコードの人まとまりに意図を示すのにも向いてる。こことか
let (|Found|_|) aType excludes name =
FSharpType.GetUnionCases aType
|> Seq.filter (fun u -> Set.contains u.Name excludes |> not)
|> Seq.tryFind (fun u -> u.Name |> String.lower = name)
let private fromString<'a> s =
let name = String.lower s
let aType = typeof<'a>
match name with
| Found aType (set []) u -> FSharpValue.MakeUnion(u, [||]) :?> 'a
| _ -> failwithf $"Unknown %s{aType.Name} '%s{s}'."
Discriminated Union を文字列から作るとき(pocof のパラメータ変換で使う)にハマる。
元は match ~ with
のところに Found
active pattern 相当のコードを置いてたので、なおさら読みやすくなってる。
関数でもいいのだけど、 Match expression と組み合わせることでより文脈読みやすくなると、個人的に感じた。
まだ引数のパターンを示すのに使うような尖った?使い方は出来てないが、徐々になじませていくか。