2024-04-21

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 と組み合わせることでより文脈読みやすくなると、個人的に感じた。

まだ引数のパターンを示すのに使うような尖った?使い方は出来てないが、徐々になじませていくか。