Reputation: 999
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
Upvotes: 2
Views: 753
Reputation: 1721
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
Upvotes: 2
Reputation: 243041
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.
Upvotes: 7