是否有一个通用函数可以识别嵌套可区分联合的情况?

Is there a generic function that identifies the cases of a nested discriminated union?

我创建了一个嵌套的可区分联合 (DU),如下所示:

type OptimizationPeriod = | All
                          | Long
                          | Short

type OptimizationCriterion = | SharpeRatio      of OptimizationPeriod
                             | InformationRatio of OptimizationPeriod
                             | CalmarRatio      of OptimizationPeriod

还有一个非嵌套 DU:

type Parallelism = Sequential | PSeq

我有一个 JSON 配置文件,其中包含定义 DU 案例的字符串。以下函数设法识别非嵌套 Parallelism DU :

的情况
let stringToDUCase<'t> (name: string) : 't =
        let dUCase =
            Reflection.FSharpType.GetUnionCases( typeof<'t> )
            |> Seq.tryFind (fun uc -> uc.Name = name)
            |> Option.map (fun uc -> Reflection.FSharpValue.MakeUnion( uc, [||] ) :?> 't)
        match dUCase with
        | Some x -> x
        | _ -> let msg = sprintf "config.json - %s is not a case in DU %A" name typeof<'t>
               failwith msg

注意:我当然是从某处复制的,因为功能有点让我头疼,抱歉作者不记得它是从哪里来的。

不幸的是,此函数无法识别嵌套 DU 的大小写:

stringToDUCase<OptimizationCriterion> config.Trading.Criterion
System.Exception: config.json - SharpeRatio All is not a case in DU FractalTypes.OptimizationCriterion

两个问题:

1) 我能够编写一个函数来处理 specificallyOptimizationCriterion DU 并且能够识别案例。是否有一个 generic 函数沿用了 stringToDUCase 可以做同样的事情?

2) 使用 OptimizationCriterion*OptimizationPeriod 类型的元组而不是嵌套 DU 会更好吗? (我可能不得不调用 stringToDUCase 两次,但这不是问题)

All这样的"empty" DU case只是一个值,但是像SharpeRatio这样的"non-empty" DU case实际上是一个接受一个值和returns 类型。在本例中,SharpeRatio 的类型为 OptimizationPeriod -> OptimizationCriterion.

您现有的 stringToDUCase 函数总是将一个空数组传递给 MakeUnion(暗示一个空的 DU 案例)。所以这是适用于任何 DU 案例的函数的修改版本:

let stringToParamDUCase<'t> (name: string) =
    Reflection.FSharpType.GetUnionCases(typeof<'t>)
    |> Seq.tryFind (fun uc -> uc.Name = name)
    |> Option.map (fun uc ->
        fun (parameters:obj []) -> Reflection.FSharpValue.MakeUnion(uc, parameters) :?> 't)
    |> Option.defaultWith (fun () ->
        failwith (sprintf "config.json - %s is not a case in DU %A" name typeof<'t>))

请注意,它 returns 是 obj [] -> 't 的函数。我还稍微简化了错误处理。

您可能会这样使用它:

let myOptimizationPeriod = stringToParamDUCase<OptimizationPeriod> "All" [||]
let f = stringToParamDUCase<OptimizationCriterion> "SharpeRatio"
let myOptimizationCriterion = f [|All|]

我认为现有的答案应该直接回答你的问题。但是,我认为有必要再提两点。首先,如果您将 OptimizationCriterion 表示为一条记录可能会更容易,因为您所有的 DU 案例都包含相同的值:

type OptimizationPeriod = 
  | All | Long | Short

type OptimizationRatio = 
  | SharpeRatio | InformationRatio | CalmanRatio

type OptimizationCriterion =
  { Ratio : OptimizationRatio
    Period : OptimizationPeriod }

这也恰好解决了你的问题,因为现在你只需要没有参数的DU,但我认为这也是更好的设计,因为你避免了重复第二个参数。

其次,我认为您真的不需要使用花哨的基于反射的自定义函数来进行反序列化。如果你想将数据存储在 JSON 中,你应该使用标准库(Newtonsoft.JSON 或 Chiron 就可以),或者你可以直接使用类似 JsonValue 的东西来编写它F# 数据,但使用自定义反射代码是导致代码无法维护的快速方法。