Recursion F#迭代嵌套字典

Recursion F#迭代嵌套字典,recursion,dictionary,f#,Recursion,Dictionary,F#,我有一个丑陋的调试功能,用于打印3级地图,实际上它们是字典。我一直在尝试转换它,这样我就可以打印出一张n级地图。这是我迄今为止的尝试: open System.Text open System.Collections.Generic let rec PrintMap x = let PrintVal (v: obj) = match v with | :? seq<KeyValuePair<_,_>> a

我有一个丑陋的调试功能,用于打印3级地图,实际上它们是字典。我一直在尝试转换它,这样我就可以打印出一张n级地图。这是我迄今为止的尝试:

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) 
输出非常糟糕,但它确实有效。使用上述输入字典:

k1 => { k2 => { k3 => foo
 }
 }

我认为你试图做的不会起作用,这与其说是理解F,不如说是理解泛型。您必须记住,类型推断和泛型都是编译时规则,因此使用在运行时确定类型的对象调用此函数将永远无法工作,因为编译器无法确定要应用的类型参数。换句话说,每次调用
PrintMap v
,编译器必须知道
v
的类型


与C#中的类似问题一样,要解决这一问题,您必须遵循。

PrintVal
中,第二种情况从不匹配,因为
seq
并不意味着任何键值对序列,无论类型为arg;这意味着让编译器推断类型args。换句话说,下划线只是用于编译时类型推断的通配符,而不是模式匹配。在这种情况下,类型arg可能被推断为
,它与测试中的任何词典都不匹配


要实现这一点,您可能必须匹配非泛型类型,例如
System.Collections.IDictionary

虽然所建议的解决方案适用于成熟的词典,但它无法处理仅
IDictionary
的实现,如F#
dict
函数返回的实现。这可以通过测试接口并使用非通用的
IEnumerable
迭代其内容来实现:

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

好的,我已经尝试更新了这个示例,使它看起来更符合F#习惯用法。我更新它的方式,我认为它应该非常接近工作,但我不完全确定为什么总是采取第二个分支。虽然它可能很诱人,但你不应该把你的问题的最终解决方案。最佳做法是创建一个包含最终代码的新答案(同时仍将相应答案标记为正确答案)。我一直想把常见问题重新阅读一遍,对不起,礼节不好,下次我会记住这一点。您好,谢谢您的回答!考虑到我相当肯定这可以在C#中完成,而只需要一些策略重载,我真的认为没有必要进行反思。@arfbtwn我不这么认为。Daniel关于编译器无法为
KeyValuePair
推断任何特定类型的观点仍然适用,尽管您可以通过C#的
动态
功能用属性访问替换反射。只是在C#中做了一点尝试,没有任何反射,我相信您是正确的,先生因为我没有成功:)
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
{ k1 => { k2 => { k3 => "foo"
 }
 }
 }