从字符串创建判别联合类型的案例

18

我正试图从字符串中创建 DU(Discriminated Union) 报告。我唯一看到的方法是通过枚举使用 Microsoft.FSharp.Reflection.FSharpType.GetUnionCases 获得 DU 报告,然后选择与字符串匹配的 UnionCase (使用 .Name),然后使用 FSharpValue.MakeUnion 创建实际的 DU 报告。

有没有更简单/更优雅的方法来做到这一点? 在我的场景中,我有一个包含数百个关键字的 DU,我必须从文件中读取字符串(关键字)并将它们转换为类型。 我通过将情况放入地图中进行了一些“优化”,但我希望有更好的方法来执行此操作。 例如,我有以下内容:

type Keyword = 
    | FOO
    | BAR
    | BAZ
    | BLAH

let mkKeywords (file: string) =
    use sr = new StreamReader(file)

    let caseMap = 
        FSharpType.GetUnionCases(typeof<Keyword>)
        |> Array.map (fun c -> (c.Name, FSharpValue.MakeUnion(c, [||]) :?> Keyword))
        |> Map.ofArray

    [
        while not sr.EndOfStream do
            let l = sr.ReadLine().Trim()

            match caseMap.TryFind l with
            | Some c -> yield c
            | None -> failwith <| "Could not find keyword: " + l
    ] 

我不知道。我刚开始学习 F# 和 .Net,所以我还不太相信自己... :) - siki
我认为这已经是最好的了。反射本质上不够优雅。 - Daniel
也许联合体不是最适合的工具。 几百个案例表明使用不当。 你想做什么? - Daniel
类型提供程序可能是您在没有代码生成的情况下实现类型安全的唯一方法,但我怀疑这样做是否值得努力。 - Daniel
@Daniel 一个包装在单个 DU 中的字典,并在其中定义了动态运算符,看起来既类型安全又方便。 类似于 type Dict<'A> = Dict of Dictionary<string,'A> with static member (?) (Dict d, key) = ... - kaefer
显示剩余3条评论
3个回答

25

我找到了这个有用的代码片段...

open Microsoft.FSharp.Reflection

let toString (x:'a) = 
    let (case, _ ) = FSharpValue.GetUnionFields(x, typeof<'a>)
    case.Name

let fromString<'a> (s:string) =
    match FSharpType.GetUnionCases typeof<'a> |> Array.filter (fun case -> case.Name = s) with
    |[|case|] -> Some(FSharpValue.MakeUnion(case,[||]) :?> 'a)
    |_ -> None

...这使得在任何DU上添加两行代码变得非常容易...

type A = X|Y|Z with
    override this.ToString() = FSharpUtils.toString this
    static member fromString s = FSharpUtils.fromString<A> s

1
toString 可以被解构替代,如下所示:let toString (x:'a) = let (case, _ ) = FSharpValue.GetUnionFields(x, typeof<'a>) case.name - Assassin
@Assassin 好的建议。已更改。 - Wallace Kelly

10

我会像这样使用模式匹配:

type Keyword = 
    | FOO
    | BAR
    | BAZ
    | BLAH


let matchKeyword (word:string) : Keyword option =
    match word with
    | "FOO"  -> Some FOO
    | "BAR"  -> Some BAR
    | "BAZ"  -> Some BAZ
    | "BLAH" -> Some BLAH
    | _      -> None

也许第一次在我的编辑器中使用正则表达式自动生成匹配语句,但这只是因为你有数百种情况。但我不确定它是否比你的解决方案更好。


这可以通过F# 5中引入的nameof运算符进行改进。 - paolo_
怎么做?@paolo_ - Robur_131
1
在匹配语句中使用 nameof FOO -> Some FOO 代替字符串字面量,这样可以避免使用字符串字面量。虽然改进不大,但是仍然有所帮助。 - paolo_
@paolo_ 它区分大小写吗? - cikatomo

9

由于这些案例没有价值,另一个选择是使用枚举:

type Keyword = 
  | FOO   = 0
  | BAR   = 1
  | BAZ   = 2
  | BLAH  = 3

let strings = ["FOO";"BAR"]
let keywords = 
  [for s in strings -> s, Keyword.Parse(typeof<Keyword>, s)]
  |> Map.ofList

然后,您可以直接使用Enum.Parse方法。


谢谢Phillip。我在考虑使用枚举,但是.Parse不是在幕后做了和我一样的事情吗? - siki
在底层,仍然存在从枚举名称到值的字符串查找。 Enum.Parse 只是稍微简短一些。 - Phillip Trelford

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接