[F#][小ネタ] Unreachableアクティブパターン

matchのネストを減らす小ネタ

まれに判別共用体の値がどのケースか分かっていることがある。 一般論としてはケースが絞り込まれる場所であらかじめケースを剥がしておくほうがよい。

しかしどうしても剥がしたいことはある。 その場合はmatchを使い、その他のパターンを例外送出でごまかす。

type MyValue =
    | Int of int
    | String of string

let onInt (value: MyValue) =
    match value with
    | Int n ->
        doSomething n

    | _ -> failwith "unreachable"

この状況で使える小ネタとして、以下のヘルパーを用意しておくと記述量が減る:

let inline private (|Unreachable|) (_: 'T) : 'U = failwith "unreachable"

let onInt (value: MyValue) =
    let (Int n | Unreachable n) = value
    doSomething n

ここで Unreachable というアクティブパターンを定義している。 必ずマッチに成功するので、let の左辺で使っても「パターンが網羅的でない」という警告が出ない。 実行時の挙動は前述の match と同じ。


Appendix. サンプルコード

try.fsharp.org のreplで見る

/// Diverging active pattern.
let inline private (|Unreachable|) (_: 'T) : 'U = failwith "unreachable"

type MyValue =
    | Int of int
    | String of string

let doSomething (n: int) = printfn "int -> %d" n

// ----------
// match
// ----------

printfn "use match:"

let onInt1 (value: MyValue) =
    match value with
    | Int n ->
        doSomething n

    | _ -> failwith "unreachable"

onInt1 (Int 1) //=> int -> 1

try
    onInt1 (String "")
    assert false
with _ -> printfn "string -> raised"

// ----------
// let
// ----------

printfn "use let:"

let onInt2 (value: MyValue) =
    let (Int n | Unreachable n) = value
    doSomething n

onInt2 (Int 2) //=> int -> 2

try
    onInt2 (String "")
    assert false
with _ -> printfn "string -> raised"

関連記事