如果通用类型是一个,则将其转换为其基础的可区分联合

Casting a generalized type to its underlying discriminated union if it is one

出于调试目的,我需要比 printfn "%A" 提供的更多关于浮点数的详细信息,并且我的所有测试代码都是通用的。所以我想我 box 并匹配类型以获得有意义的输出。

但是,类型可以是选项类型或 Result<_, _> 类型 (Success/Fail)。这些类型仅在您将它们视为可区分的联合时进行模式匹配,但如果我对它们进行类型测试,它们将不会被命中:

// Illustrative example of what I'm trying to achieve (I think)
let rec mkStr v =
    match box v with
    | :? double as dbl -> Str.fromFloat dbl
    | :? int64 as i -> i.ToString()
    | _ -> 
        match v :> obj with 
        | :? Result<_, _> as res ->   // this will never succeed
            match res with
            | Success s -> mkStr s
            | Fail _ -> "Fail"
        | x ->  
            x.ToString()

我尝试了多种boxing/unboxing/casting,上面是其中一种。当我调试时,我可以清楚地看到 Success 正在通过,调试器甚至不显示它是 Result。我知道 DU 中的每个鉴别器本身就是一种类型,所以我了解发生了什么,但是如果编译器发现它与 Success 或 [=17= 中的一个匹配,我该如何告诉编译器 "treat as" DU ]?

TLDR:给定一个通用类型,如果有的话,我如何测试并将该类型转换为底层 DU?

(PS:我意识到这通常不是解决 DU 的方法,但就像 printf "%A" 存在一样,我假设偶尔需要这样做,就像在这种情况下一样)

@kvb 在评论中发布的使用引号获得模式匹配支持的解决方案是一个不错的技巧。我过去遇到过类似的问题并使用了一个稍微不同的技巧 - 不是使用反射来解构和检查值,而是可以使用反射来调用具有正确类型参数的泛型方法。这是一个最小的例子:

type MakeString = 
  static member Make<'T>(o:'T) =
    match box o with
    | :? float as f -> sprintf "Float: %A" f
    | o ->
        if typeof<'T>.Name = "FSharpOption`1" then
          let tys = typeof<'T>.GetGenericArguments()
          typeof<MakeString>.GetMethod("MakeOption")
            .MakeGenericMethod(tys).Invoke(null, [| o |]) :?> string
        else
          failwith "Unknown"

  static member MakeOption<'T>(o:option<'T>) =
    match o with
    | None -> "None"
    | Some v -> "Some " + MakeString.Make(v)

MakeString.Make(Some 3.14)

当调用 Make 时使用类型为 option<'T2> 的值,对于某些我们无法静态知道的 'T2,我们通过查看使用反射找到 'T2泛型类型参数,然后我们使用 'T2 作为类型参数调用 MakeOption - 所以在 MakeOption 中,我们现在(再次)知道选项中包含的值的静态类型。

这样做的一个可能的好处是它都使用静态类型工作,因此当值表示为 null 时它将免费工作(因此直接调用 None.GetType() 会抛出一个例外)。 previous answer posted by @kvb 也处理这个问题,但使用特殊情况来处理这种情况。