Search code examples
f#overload-resolution

Why does F# require type placeholders for ToDictionary?


given

[
    1,"test2"
    3,"test"
]
|> dict
// turn it into keyvaluepair sequence
|> Seq.map id

|> fun x -> x.ToDictionary<_,_,_>((fun x -> x.Key), fun x -> x.Value)

which fails to compile if I don't explicitly use the <_,_,_> after ToDictionary.
Intellisense works just fine, but compilation fails with the error: Lookup on object of indeterminate type based on information prior to this program point So, it seems, Intellisense knows how to resolve the method call.

This seems to be a clue

|> fun x -> x.ToDictionary<_,_>((fun x -> x.Key), fun x -> x.Value)

fails with

Type constraint mismatch.  
The type 'b -> 'c  is not compatible with type IEqualityComparer<'a>     
The type 'b -> 'c' is not compatible with the type 'IEqualityComparer<'a>'  
(using external F# compiler)

x.ToDictionary((fun x -> x.Key), id)

works as expected as does

let vMap (item:KeyValuePair<_,_>) = item.Value
x.ToDictionary((fun x -> x.Key), vMap)

I've replicated the behavior in FSI and LinqPad.

As a big fan of and avid reader of Eric Lippert I really want to know what overload resolution, (or possibly extension methods from different places) are conflicting here that the compiler is confused by?


Solution

  • Even though the types are known ahead, the compiler's getting confused between the overload which takes an element selector and a comparer. The lambda compiles to FSharpFunc rather than the standard delegate types in C# like Action or Func, and issues do come up translating from one to the other. To make it work, you can :

    Supply a type annotation for the offending Func

    fun x -> x.ToDictionary((fun pair -> pair.Key), (fun (pair : KeyValuePair<_, _>) -> pair.Value)) //compiles
    

    or name the argument as a hint

    fun x -> x.ToDictionary((fun pair -> pair.Key), elementSelector = (fun (pair) -> pair.Value))
    

    or force it to pick the 3 argument version:

    x.ToLookup((fun pair -> pair.Key), (fun (pair) -> pair.Value), EqualityComparer.Default)
    

    Aside

    In your example,

    let vMap (item:KeyValuePair<_,_>) = item.Value
    x.ToDictionary((fun x -> x.Key), vMap)
    

    you would explicitly need to annotate vMap because the compiler cannot find out what type the property exists on without another pass. For example,

    List.map (fun x -> x.Length) ["one"; "two"] // this fails to compile
    

    This is one of the reasons why the pipe operator is so useful, because it allows you to avoid type annotations:

    ["one"; "two"] |> List.map (fun x -> x.Length) // works
    
    List.map (fun (x:string) -> x.Length) ["one"; "two"] //also works