2

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!

Edit

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.

Solution

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
 }
 }
arfbtwn
  • 328
  • 2
  • 11
  • As tempting as it may be, you shouldn't put your final solution in your question. Best practice is to create a new answer with your final code in it (while still marking the appropriate answer as correct). – N_A Jun 16 '14 at 18:01
  • I have been meaning to give the FAQ a read over, sorry for the bad etiquette, I'll keep that in mind for next time. – arfbtwn Jun 17 '14 at 14:42

3 Answers3

2

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.

Daniel
  • 47,404
  • 11
  • 101
  • 179
0

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.

Community
  • 1
  • 1
Tim Rogers
  • 21,297
  • 6
  • 52
  • 68
  • Ok, I've tried to update the example so it looks more in line with F# idioms. The way I've updated it I think it should be quite close to working but I'm not entirely certain why the [type test](http://msdn.microsoft.com/en-gb/library/dd547125.aspx) is always taking the second branch. – arfbtwn Jun 16 '14 at 14:36
0

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"
 }
 }
 }
kaefer
  • 5,491
  • 1
  • 15
  • 20
  • Hi, thanks for the answer! I don't really think that reflection should be necessary, considering I'm fairly certain that this could be done in C# with nothing more than some strategic overloading. – arfbtwn Jun 17 '14 at 22:31
  • @arfbtwn I don't think so. Daniel's point about the compiler being unable to infer any specific type for `KeyValuePair<_,_>` still applies, albeit you may be able to replace reflection with property access through C#'s `dynamic` feature. – kaefer Jun 18 '14 at 07:25
  • just made a little attempt in C# without any reflection, I believe you are correct sir! - in that I was not successful :) – arfbtwn Jun 18 '14 at 21:04