F# 如何从装箱的 Map 对象中提取键

Jan*_*lme 2 reflection f#

这是这个帖子那个帖子的后续

我需要编写一个函数,它接受一个对象(obj类型)和一个键(也是一个obj类型),如果对象恰好是一个 Map,Map<'k,'v>那么它是 any,然后提取它的键和值。

困难在于我无法使用泛型类型对函数进行参数化,并且我们无法对泛型类型的对象进行模式匹配。

我不熟悉 F# 反射,但是一旦我知道它的键,我就找到了一种获取 Map 值的方法。使用此示例代码:

module TestItem = 
    open System
    open Microsoft.FSharp.Reflection

    // some uninteresting types for this example, could be anything arbitrary
    type Foo = {argF1 : string; argF2 : double; argF3 : bool[]}
    type Bar = {argB1 : string; argB2 : double; argB3 : Foo[]}

    // and their instances
    let foo1 = {argF1 = "foo1"; argF2 = 1.0; argF3 = [| true  |]}
    let foo2 = {argF1 = "foo2"; argF2 = 2.0; argF3 = [| false |]}

    let bar1 = {argB1 = "bar1"; argB2 = 10.0; argB3 = [| foo1 |]}
    let bar2 = {argB1 = "bar2"; argB2 = 20.0; argB3 = [| foo2 |]}

    // a Map type
    type Baz = Map<String,Bar>    
    let baz : Baz = [| ("bar1", bar1); ("bar2", bar2) |] |> Map.ofArray

    let item (oMap : obj) (key : obj) : unit =
        let otype = oMap.GetType()

        match otype.Name with
        | "FSharpMap`2" -> 
            printfn "  -Map object identified"
            let prop  = otype.GetProperty("Item")

            try
                let value = prop.GetValue(oMap, [| key |]) 
                printfn "  -Value associated to key:\n %s" (value.ToString())
            with
            | _             ->  
                printfn "  -Key missing from oMap"
        | _             ->  
            printfn "  -Not a Map object"

    [<EntryPoint>]
    let main argv =
        printfn "#test with correct key"
        let test = item baz "bar1"

        printfn "\n#test with incorrect key"
        let test = item baz "bar1X"

        Console.ReadKey() |> ignore
        0 // return exit code 0
Run Code Online (Sandbox Code Playgroud)

运行上面的代码会向控制台输出以下内容:

#test with correct key
  -Map object identified
  -Value associated to key:
 {argB1 = "bar1";
 argB2 = 10.0;
 argB3 = [|{argF1 = "foo1";
            argF2 = 1.0;
            argF3 = [|true|];}|];}

#test with incorrect key
  -Map object identified
  -Key missing from oMap
Run Code Online (Sandbox Code Playgroud)

现在,为了解决我的问题,我只需要找到一种方法来从 oMap 对象中提取键。

我的问题:如何完成下面的代码以返回 obj[] 类型的 oMap 键,如果 oMap 确实是一个装箱的 Map 对象?

module CompleteThis =
    open System
    open Microsoft.FSharp.Reflection

    let keys (oMap : obj) (key : obj) : obj[] =
        let otype = oMap.GetType()

        match otype.Name with
        | "FSharpMap`2" -> 
            printfn "  -Map object identified"

            (* COMPLETE HERE *)
            Array.empty // dummy
        | _             ->  
            printfn "  -Not a Map object"
            Array.empty // return empty array
Run Code Online (Sandbox Code Playgroud)

Tom*_*cek 7

如果你有一个类型化的 map map,这样做的一种方法是使用序列表达式迭代 map 并使用你得到的Key属性获取键KeyValuePair

[| for kvp in map -> box kvp.Key |]
Run Code Online (Sandbox Code Playgroud)

使用反射(与您Item在其他示例中调用的方式相同)重建代码以执行此操作将是一场噩梦。您可以做的一个很好的技巧是将其放入通用方法中:

type KeyGetter = 
  static member GetKeys<'K, 'V when 'K : comparison>(map:Map<'K, 'V>) = 
    [| for kvp in map -> box kvp.Key |]
Run Code Online (Sandbox Code Playgroud)

现在,您可以GetKeys通过反射访问该方法,获取您的类型参数Map并使用这些作为'K'V该方法的类型参数,并使用您的oMap作为参数调用该方法:

let keys (oMap : obj) : obj[] =
    let otype = oMap.GetType()    
    match otype.Name with
    | "FSharpMap`2" ->          
        typeof<KeyGetter>.GetMethod("GetKeys")
            .MakeGenericMethod(otype.GetGenericArguments())
            .Invoke(null, [| box oMap |]) :?> obj[]
    | _             ->  
        Array.empty
Run Code Online (Sandbox Code Playgroud)

这有效。但是,我应该补充一点,您实际上需要这样做的事实表明您的系统很可能没有完全设计好,因此我会考虑更改您的应用程序的设计,以便您不需要这样做的东西。当然,做这样的事情有一些很好的理由,但它不应该太普遍。

  • 4- ...因此我尝试使用 F# 反射来规避 F# 类型系统。当 Excel 的替代品成为强类型的那一天,我将是第一个跳槽的人,但实际上这需要一段时间。我很想知道您是否认为这个用例可能是您提到的“充分理由”之一?感谢您的帮助。 (2认同)