Reputation: 328
I have an ugly debugging function for printing a 3 level map, in fact they're dictionaries. I've been trying to convert it so that I can print off an n-level map. This is my attempt so far:
open System.Text
open System.Collections.Generic
let rec PrintMap x =
let PrintVal (v: obj) = match v with
| :? seq<KeyValuePair<_,_>> as vm -> "{ " + PrintMap vm + " }"
| _ -> sprintf "Value: %s" (v.ToString())
let sb = StringBuilder()
for KeyValue(key,value) in x do
sb.AppendLine(sprintf "%s => %s" (key.ToString()) (PrintVal value)) |> ignore
sb.ToString()
type dict3 = Dictionary<string,obj>
type dict2 = Dictionary<string,dict3>
type dict1 = Dictionary<string,dict2>
let k1 = "k1"
let k2 = "k2"
let k3 = "k3"
let v = "foo" :> obj
let dict = dict1()
dict.[k1] <- dict2()
dict.[k1].[k2] <- dict3()
dict.[k1].[k2].[k3] <- v
printf "%s" (PrintMap dict)
I'd rather not switch the nested dictionaries to their F# equivalents (at least for input) as the real thing deals with them.
You can probably deduce from what I'm almost certain is a naive attempt at an Active Pattern, not even mentioning the StringBuilder
, that I'm quite new to F#. It seems my difficulty is telling the difference between a dictionary and 'everything else' when looking at the value, and informing the type inference system that the values aren't the same type as the dictionary in the 'x' parameter.
Any hints?
Thankyou!
A small elaboration --- sometimes I get just a dict2
or dict3
type instead of the full dict1
so that's one of the reasons I'm trying to make this more generic.
Thanks to Daniel:
let rec PrintMap x =
let PrintVal (v: obj) = match v with
| :? IDictionary as vd -> "{ " + PrintMap vd + " }"
| _ -> sprintf "%s" (v.ToString())
let sb = StringBuilder()
for key in x.Keys do
sb.AppendLine(sprintf "%s => %s" (key.ToString()) (PrintVal x.[key])) |> ignore
sb.ToString()
The output is pretty ugly, but it certainly works. With the above input dictionary:
k1 => { k2 => { k3 => foo
}
}
Upvotes: 2
Views: 489
Reputation: 5741
While the solution suggested by Daniel works well with full-blown dictionaries, it cannot handle implementations of only IDictionary<_,_>
, like those that are returned by the F# dict
function. This can be achieved by testing for the interface and using non-generic IEnumerable
to iterate its contents:
let getProp name x =
x.GetType().InvokeMember(
name, BindingFlags.Public ||| BindingFlags.InvokeMethod |||
BindingFlags.Instance ||| BindingFlags.GetProperty, null, x, null )
let rec PrintMap x =
match x.GetType().GetInterface "IDictionary`2" with
| null -> sprintf "%A" x
| _ ->
let sb = StringBuilder()
sb.Append "{ " |> ignore
for o in unbox<System.Collections.IEnumerable> x do
let (k, v) = getProp "Key" o, getProp "Value" o
sb.AppendLine(sprintf "%s => %s" (string k) (PrintMap v)) |> ignore
sb.Append " }" |> ignore
string sb
dict["k1", dict["k2", dict["k3", "foo"]]]
|> PrintMap
The output (slightly different):
{ k1 => { k2 => { k3 => "foo"
}
}
}
Upvotes: 0
Reputation: 47904
In PrintVal
, the second case never matches because seq<KeyValuePair<_,_>>
doesn't mean any sequence of key value pairs regardless of type args; it means leave it to the compiler to infer the type args. In other words, the underscores are only wildcards for the purpose of compile-time type inference, not pattern matching. In this case, the type args are likely inferred to be <obj, obj>
which doesn't match any of the dictionaries in your test.
To pull this off, you'll likely have to match on a non-generic type, such as System.Collections.IDictionary
.
Upvotes: 2
Reputation: 21713
What I think you're trying to do will not work, and it's not so much about understanding F# here as understanding generics. You have to remember that both type inference and generics are compile-time rules so calling this function with objects whose types are determined at runtime will never work, because the compiler cannot determine which type parameters you want to apply. In other words, every time you call PrintMap v
, the compiler must know the type of v
.
In common with similar problems in C#, to solve this, you would have to go down the route of dynamically invoking .NET methods using reflection.
Upvotes: 0