F#:在 DU 案例上进行类型匹配,使其更通用

F#: type matching on DU cases, make this slightly more generic

this previous question 中,有一个很好的解决方案来询问一个对象是否是一个特定的联合案例:

let isUnionCase (c : Expr<_ -> 'T>)  = 
    match c with
    | Lambdas (_, NewUnionCase(uci, _)) ->
        let tagReader = Microsoft.FSharp.Reflection.FSharpValue.PreComputeUnionTagReader(uci.DeclaringType)
        fun (v : 'T) -> (tagReader v) = uci.Tag
    | _ -> failwith "Invalid expression"

太棒了。如果我有:

type Dog = 
    | Spaniel
    | Shepherd
type Cat =
    | Tabby
    | Manx
type Animal
    | Dog of Dog
    | Cat of Cat

我可以通过 isUnionCase <@ Animal.Dog @> someAnimal.

询问任何特定的 Animal 是否是特定的动物

我想做的是:

let typesMatch (c:Animal) t = isUnionCase t c

let rec typematch animals types =
match (animals, types) with
| ([], []) -> true
| (animal::atail, ty::tytail) -> if typesMatch animal ty then typematch atail tytail else false
| (_, _) -> false

typematch [ Animal.Dog(Spaniel); Animal.Cat(Tabby) ] [ <@ Animal.Dog @> ; <@ Animal.Cat @>]

上生成编译器错误

原因是第二个列表无效,因为它不是同质的,即使它们都是动物案例。

如何充分泛化这一点,以便您可以询问谓词 "does this list of objects which are all cases of a discriminated union match the list of expressions describing their expected case types?"

使用无类型引号 <@@ ... @@> 而不是类型化引号,并使用可以处理这些的 isUnionCase 形式:

open Microsoft.FSharp.Quotations.Patterns
open Microsoft.FSharp.Reflection

let rec isUnionCase = function
| Lambda (_, expr) | Let (_, _, expr) -> isUnionCase expr
| NewTuple exprs -> 
    let iucs = List.map isUnionCase exprs
    fun value -> List.exists ((|>) value) iucs
| NewUnionCase (uci, _) ->
    let utr = FSharpValue.PreComputeUnionTagReader uci.DeclaringType
    box >> utr >> (=) uci.Tag
| _ -> failwith "Expression is no union case."

type Dog = 
    | Spaniel
    | Shepherd
type Cat =
    | Tabby
    | Manx
type Animal =
    | Dog of Dog
    | Cat of Cat

let typesMatch (c:Animal) t = isUnionCase t c

let rec typematch animals types =
    match (animals, types) with
    | ([], []) -> true
    | (animal::atail, ty::tytail) -> if typesMatch animal ty then typematch atail tytail else false
    | (_, _) -> false

typematch [ Animal.Dog(Spaniel); Animal.Cat(Tabby) ] [ <@@ Animal.Dog @@> ; <@@ Animal.Cat @@>]
|> printfn "Result: %b"

System.Console.ReadKey true |> ignore

此外,我使用了 isUnionCase 的改进版本,如 here 所述,它可以处理如下表达式:

isUnionCase <@ Spanial, Shepherd @>

...匹配任何西班牙犬或牧羊犬。