Why does F# require type placeholders for ToDictionary?

196 views Asked by At

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?

2

There are 2 answers

6
Asti On BEST ANSWER

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
3
user3685285 On

The short answer:

The extension method ToDictionary is defined like this:

static member ToDictionary<'TSource,_,_>(source,_,_)

but is called like this:

source.ToDictionary<'TSource,_,_>(_,_)

The long answer:

This is the F# type signature of the function you are calling from msdn.

static member ToDictionary<'TSource, 'TKey, 'TElement> : 
    source:IEnumerable<'TSource> *
    keySelector:Func<'TSource, 'TKey> *
    elementSelector:Func<'TSource, 'TElement> -> Dictionary<'TKey, 'TElement>

But I only specified two regular parameters: keySelector and elementSelector. How come this has a source parameter?!

The source parameter is actually not put in the parenthesis, but is passed in by saying x.ToDictionary, where x is the source parameter. This is actually an example of a type extension. These kinds of methods are very natural in a functional programming language like F#, but more uncommon in an object oriented language like C#, so if you're coming from the C# world, it will be pretty confusing. Anyway, if we look at the C# header, it is a little easier to understand what is going on:

public static Dictionary<TKey, TElement> ToDictionary<TSource, TKey, TElement>(
    this IEnumerable<TSource> source,
    Func<TSource, TKey> keySelector,
    Func<TSource, TElement> elementSelector
)

So the method is defined with a "this" prefix on a first parameter even though it is technically static. It basically allows you to add methods to already defined classes without re-compiling or extending them. This is called prototyping. It's kinda rare if you're a C# programmer, but languages like python and javascript force you to be aware of this. Take this example from https://docs.python.org/3/tutorial/classes.html:

class Dog:

tricks = []             # mistaken use of a class variable

def __init__(self, name):
    self.name = name

def add_trick(self, trick):
    self.tricks.append(trick)

>>> d = Dog('Fido')
>>> e = Dog('Buddy')
>>> d.add_trick('roll over')
>>> e.add_trick('play dead')
>>> d.tricks                # unexpectedly shared by all dogs
['roll over', 'play dead']

The method add_trick is defined with self as a first parameter, but the function is called as d.add_trick('roll over'). F# actually does this naturally as well, but in a way that mimics the way the function is called. When you declare:

member x.doSomething() = ...

or

member this.doSomething() = ...

Here, you are adding function doSomething to the prototype (or class definition) of "x"/"this". Thus, in your example, you actually have three type parameters, and three regular parameters, but one of them is not used in the call. All you have left is to declare the key selector function, and the element selector function, which you did. That's why it looks weird.