【问题标题】:How to enumerate a discriminated union in F#?如何在 F# 中枚举有区别的联合?
【发布时间】:2011-10-23 05:58:51
【问题描述】:

如何在 F# 中枚举可区分联合的可能“值”?

我想知道是否有类似Enum.GetValues(Type) 之类的用于受歧视的工会,我不确定我会列举什么样的数据。我想为每个选项生成一个可区分联合的列表或数组。

【问题讨论】:

    标签: f# discriminated-union


    【解决方案1】:

    是的,F# 在 .NET 的反射之上构建了自己的反射层,以帮助您理解特定于 F# 的类型,例如区分联合。下面的代码可以让您枚举工会的案例:

    open Microsoft.FSharp.Reflection
    
    type MyDU =
        | One
        | Two
        | Three
    
    let cases = FSharpType.GetUnionCases typeof<MyDU>
    
    for case in cases do printfn "%s" case.Name
    

    【讨论】:

    • 如果您想获得不受访问限制的案例,请不要忘记将BF.Public ||| BF.NonPublic where BF=System.Reflection.BindingFlags 传递给GetUnionCases
    【解决方案2】:

    稍微扩展罗伯特的例子——即使你没有可区分联合的实例,你也可以使用 F# 反射来获取关于 type 的信息(例如 types 个别案例的论点)。以下扩展了 Robert 的示例,它还打印了参数的类型:

    open Microsoft.FSharp.Reflection
    
    let ty = typeof<option<int>>
    let cases = FSharpType.GetUnionCases ty
    
    printfn "type %s =" ty.FullName
    for case in cases do 
      printf "| %s" case.Name 
      let fields = case.GetFields()
      if fields.Length > 0 then
        printf " of"
      for fld in fields do
        printf " %s " fld.PropertyType.FullName
      printfn ""
    

    例如,对于option&lt;int&gt; 类型,你会得到(我稍微简化了输出):

    type Microsoft.FSharp.Core.FSharpOption`1[System.Int32] =
      | None
      | Some of System.Int32
    

    此信息有许多有趣的用途 - 例如,您可以从 F# 联合生成 DB 模式或创建将 XML 解析为可区分联合(描述结构)的函数。我谈到了XML processing sample at GOTO conference earlier this year

    【讨论】:

    【解决方案3】:

    如果您的可区分联合仅由普通标识符组成(没有存储任何数据的情况,这可能是您需要的:gist

    open Microsoft.FSharp.Reflection
    
    module SimpleUnionCaseInfoReflection =
    
      // will crash if 'T contains members which aren't only tags
      let Construct<'T> (caseInfo: UnionCaseInfo)                   = FSharpValue.MakeUnion(caseInfo, [||]) :?> 'T
    
      let GetUnionCaseInfoAndInstance<'T> (caseInfo: UnionCaseInfo) = (caseInfo, Construct<'T> caseInfo)
    
      let AllCases<'T> = 
        FSharpType.GetUnionCases(typeof<'T>)
        |> Seq.map GetUnionCaseInfoAndInstance<'T>
    
    #load "SimpleUnionCaseInfoReflection.fs"
    
    type Foos = Foo | Bar | Baz
    SimpleUnionCaseInfoReflection.AllCases<Foos> |> Seq.iter (fun (caseInfo, instance) ->printfn "name: %s instance: %O is Bar? : %b" caseInfo.Name instance (instance.Equals(Foos.Bar)))
    
    (*
    > name: Foo instance: FSI_0055+Foos is Bar? : false
    > name: Bar instance: FSI_0055+Foos is Bar? : true
    > name: Baz instance: FSI_0055+Foos is Bar? : false
    *)
    

    【讨论】:

      【解决方案4】:

      如果没有实例,很难看出这如何可能起作用,因为歧视工会可以承载价值。

      如果你有这样的类型,例如:

      type Status = Success of string | Error of System.Exception | Timeout
      

      在这种情况下,除了您的数组要包含的 Success 或 Error 之外,您还想包含什么?

      【讨论】:

      • 这不是 Microsoft.FSharp.Reflection.FSharpType 和 Microsoft.FSharp.Reflection.FSharpValue 的用途吗?
      • 如果你也有一个实例那么肯定,但对我来说,问题似乎是关于你只有类型(在这种情况下是状态)但没有实例的情况。在这种情况下,除了 Type 数组之外,我看不出你怎么能得到任何有用的东西。可能只是我误解了这个问题。
      • 有趣的问题,我真的不知道我能用它做什么。大多数情况下,如果我知道该放什么,那么使用有区别的联合并没有多大帮助(像枚举或记录之类的东西可能更合适)。
      • 当然,即使您没有实例,这也很有用! F# 反射为您提供有关 type 的信息(因此您将获得标签名称和携带参数的类型)。您可以使用此信息从 F# 联合生成 DB 架构,或创建将 XML 解析为可区分联合(描述结构)的函数。参见例如tomasp.net/blog/goto-loosely-structured-data.aspx
      猜你喜欢
      • 1970-01-01
      • 2012-12-12
      • 2017-10-03
      • 1970-01-01
      • 2018-11-24
      • 1970-01-01
      • 1970-01-01
      • 2013-10-27
      相关资源
      最近更新 更多