【问题标题】:F# discriminated union enumeration using reflection instead of an enumerationF# 区分联合枚举使用反射而不是枚举
【发布时间】:2012-12-12 13:36:43
【问题描述】:

我在 F# 中使用反射(例如How to enumerate a discriminated union in F#?)枚举了一些可区分联合中的值。我想使用从使用反射获得的值来生成不同的记录类型,这些记录类型由我正在枚举的有区别的联合组成,但我不确定如何将类型 UnionCaseInfo 转换为实际的联合案例。有可能进行这样的演员吗?下面的代码正是我想要做的(区分联合中的值不同,变量名也不同)。我知道我可以使用枚举,但我宁愿不使用它们而不是区分联合。

open System
open Microsoft.FSharp.Collections
open Microsoft.FSharp.Reflection

let GetUnionCaseName (x:'a) = 
    match FSharpValue.GetUnionFields(x, typeof<'a>) with
    | case, _ -> case.Name  

type shape =
    | Square
    | Circle
    | Triangle
    | Other

type color =
    | Black
    | Red
    | Blue
    | Green
    | White

type coloredShape = { Shape: shape; Color: color }

let shapeCases = FSharpType.GetUnionCases typeof<shape>
let colorCases = FSharpType.GetUnionCases typeof<color>

let boardOfRelevantPossibilities = Microsoft.FSharp.Collections.Array2D.init<coloredShape> 5 3 (fun x y -> {Shape = Other; Color = Black})

let OtherShape = GetUnionCaseName(shape.Other)
let rand = Random()

for shapeCase in shapeCases do
    // Is there a way to do the following comparison this without using string comparisons
    if not (shapeCase.Name.Equals OtherShape) then
        for colorCase in colorCases do
            let mutable addedToBoard = false

            while not addedToBoard do
                let boardRowIndex = rand.Next(0,4)
                let boardColumnIndex = rand.Next(0,2)

                if boardOfRelevantPossibilities.[boardRowIndex,boardColumnIndex].Shape.Equals shape.Other then
                    addedToBoard <- true

                    // I want to utilize colorCase instead of other and shapeCase instead of black
                    boardOfRelevantPossibilities.[boardRowIndex,boardColumnIndex] <- {Shape = Other; // Shape should be determined by shapeCase instead of Other  
                        Color = White } // Color should be determined by colorCase instead of White

Console.ReadKey() |> ignore

我将我的代码重构为以下内容:

open System
open Microsoft.FSharp.Collections
open Microsoft.FSharp.Reflection

let allUnionCases<'T>() =
    FSharpType.GetUnionCases(typeof<'T>)
    |> Array.map (fun case -> FSharpValue.MakeUnion(case, [||]) :?> 'T)

type shape =
    | Square
    | Circle
    | Triangle
    | Other

type color =
    | Black
    | Red
    | Blue
    | Green
    | White

type coloredShape = { Shape: shape; Color: color }

let shapeCases = FSharpType.GetUnionCases typeof<shape>
let colorCases = FSharpType.GetUnionCases typeof<color>

let numberOfRows = 5
let numberOfColumns = 3
let boardOfRelevantPossibilities = Microsoft.FSharp.Collections.Array2D.init<coloredShape> numberOfRows numberOfColumns (fun x y -> {Shape = Other; Color = Black})

let rand = Random()

for shapeCase in allUnionCases<shape>() do
    // No string comparison anymore
    if shapeCase <> shape.Other then
        for colorCase in allUnionCases<color>() do
            let mutable addedToBoard = false

            while not addedToBoard do
                let boardRowIndex = rand.Next(0,numberOfRows)
                let boardColumnIndex = rand.Next(0,numberOfColumns)

                if boardOfRelevantPossibilities.[boardRowIndex,boardColumnIndex].Shape.Equals shape.Other then
                    addedToBoard <- true
                    // utilizing colorCase and shapeCase to create records to fill array
                    boardOfRelevantPossibilities.[boardRowIndex,boardColumnIndex] <- {Shape = shapeCase; Color = colorCase } 


printfn "%A" boardOfRelevantPossibilities
Console.ReadKey() |> ignore

这种新的重构结合了反射以通过可区分联合进行枚举,同时允许我生成由这些可区分联合组成的不同记录类型。我还发现了两个完全不同的错误,它们在重构代码中得到了修复。

【问题讨论】:

  • 字符串比较到底有什么问题 - 它应该可以正常工作?
  • 我宁愿直接与形状进行比较。其他,事后看来这真的没关系,我猜
  • 如果你知道没有参数,你可以使用FSharpValue.MakeUnion,它可能会做你想做的事
  • 投到shape:{ Shape = FSharpValue.MakeUnion(shapeCase, [| |]) :?&gt; shape; Color = White }
  • 这种方式转换的问题是以下代码(当插入到我发布的原始代码中(这是代码的最顶层 sn-p)时):boardOfRelevantPossibilities.[boardRowIndex,boardColumnIndex] &lt;- {Shape = FSharpValue.MakeUnion(colorCase, [| |]) :?&gt; shape; Color = FSharpValue.MakeUnion(shapeCase, [| |]) :?&gt; color } 实际构建并将生成以下代码运行时错误:System.InvalidCastException 运行代码时。

标签: f# f#-3.0


【解决方案1】:

说实话,我更喜欢在静态方法中手动枚举所有并集,而不是通过反射创建它们。

正如@John 所说,您需要使用FSharpValue.MakeUnion 多一步:

let allUnionCases<'T>() =
    FSharpType.GetUnionCases(typeof<'T>)
    |> Array.map (fun case -> FSharpValue.MakeUnion(case, [||]) :?> 'T)

while 循环中,您应该使用= 而不是Equals,并使用没有完全限定名称的联合案例 (shape.Other)。

/// Use type params for clarity; type inference should work fine without them
for shapeCase in allUnionCases<shape>() do
    for colorCase in allUnionCases<color>() do
        let mutable addedToBoard = false
        while not addedToBoard do
            let r = rand.Next(0,4)
            let c = rand.Next(0,2)
            if boardOfRelevantPossibilities.[r, c].Shape = Other then
                addedToBoard <- true
                boardOfRelevantPossibilities.[r, c] <- { Shape = shapeCase; 
                                                         Color = colorCase } 

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2016-07-13
    • 2021-12-05
    • 2011-10-23
    • 2012-11-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多