F# How to extract keys from a boxed Map object

浪子不回头ぞ 提交于 2019-12-24 07:38:02

问题


This is a follow-up of this post and that post.

I need to write a function which takes an object (obj type) and a key (also an obj type), and if the object happens to be a Map, that is any Map<'k,'v> then extracts its keys and values.

The difficulty is that I cannot parametrize the function with generic types and that we cannot pattern-match objects on generic types.

I am not familiar with F# Reflection, but I found a way to get the Map's values, once I know its keys. With this example code :

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

Running the code above ouputs the following to the Console :

#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

Now, to solve my problem, I would just need to find a way to extract the keys from the oMap object.

My question : how to complete the code below to return the oMap keys, of type obj[], if oMap is indeed a boxed Map object?

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

回答1:


If you have a typed map map, one way of doing this is to iterate over the map using a sequence expression and get the keys using the Key property of the KeyValuePair that you get:

[| for kvp in map -> box kvp.Key |]

Reconstructing the code to do this using reflection (in the same way in which you invoke Item in your other example) would be a nightmare. A nice trick that you can do is to put this into a generic method:

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

Now, you can access the GetKeys method via reflection, get the type arguments of your Map and use those as 'K and 'V of the method, and invoke the method with your oMap as an argument:

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

This works. However, I should add that I the fact that you actually need to do this is a sign that your system is most likely not exactly well designed, so I would consider changing the design of your application so that you do not need to do this kind of thing. There are, of course, some good reasons for doing something like this, but it should not be too common.




回答2:


This will return the keys as an array of strings.

    let keys (oMap : obj) =
        let otype = oMap.GetType()

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

            (* COMPLETE HERE *)
            let map = oMap :?> Map<string, Bar>
            let keys = map |> Map.toArray |> Array.map fst
            keys
        | _             ->  
            printfn "  -Not a Map object"
            Array.empty // return empty array

    [<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"

        let keys = keys baz

        Console.ReadKey() |> ignore
        0 // return exit code 0


来源:https://stackoverflow.com/questions/59249632/f-how-to-extract-keys-from-a-boxed-map-object

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!