7

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?

Guy Coder
  • 24,501
  • 8
  • 71
  • 136
Maslow
  • 18,464
  • 20
  • 106
  • 193

2 Answers2

2

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
Asti
  • 12,447
  • 29
  • 38
  • Sorry, this answer is wrong. The keySelector and elementSelector functions are defined correctly. Your method only compiles because it is by default using `x.ToDictionary<_,_,_>` behind the scenes, and you haven't explained why, which is what the guy asked. To test this, I moused over your code, and saw in the function tip that it is actually compiling to `x.ToDictionary<_,_,_>`. Read my answer for details. – user3685285 Jan 06 '17 at 17:17
  • 1
    It's an overload mismatch. It even picks the right overload if you just supply `x.ToLookup((fun pair -> pair.Key), (fun (pair) -> pair.Value), EqualityComparer.Default)`. Your answer just explains what extension methods are. – Asti Jan 06 '17 at 18:04
  • While I don't agree, thank you for explaining why you downvoted. – Asti Jan 06 '17 at 18:07
  • Ok, well I just downvoted because I think you provided a workaround that shields OP from understanding what is going on. Rather, he already had working code, and just needed an explanation of why it worked that way. But your answer does provide more insight into that 'clue' which I failed to mention, so I'll remove the downvote if you edit. (I can't undo unless it's edited). – user3685285 Jan 06 '17 at 18:29
  • 1
    interesting that the `fun x -> x.ToDictionary((fun pair -> pair.Key), elementSelector = (fun (pair) -> pair.Value))` version does work, I still want to know what methods (which could be anywhere in the .netframework are being considered as possible candidates for this) As far as I can tell neither of the two that have been mentioned, would fail to infer the type of, nor the types involved inside the 2nd `FSharpFunc` argument – Maslow Jan 06 '17 at 18:34
  • @Maslow Either way, I would suggest you just use `dict` or similar. Extension methods in F# have usually leads to some mild confusion. – Asti Jan 11 '17 at 15:58
1

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.

Community
  • 1
  • 1
user3685285
  • 6,066
  • 13
  • 54
  • 95
  • 2
    Apologies, but how is this answering the question? The question is, as far as I understand, why you need to add the type parameters (even if you just define '_') in this case. I.e. why is is the resolver not considering ToDictionary<_,_,_>(…) for ToDictionary(…)? – Benjamin Podszun Jan 06 '17 at 16:42
  • @BenjaminPodszun is close to the secondary question, but it's more why does it seem intellisense knows what to do while, compilation has no idea. The main is in the exact wording of the question, what overloads/extension methods is the compiler fighting between? not the actual answer, but wild guesses: `Enumerable.ToDictionary<_,>(...)` is fighting with `Enumerable.ToDictionary<_,_,_>(..)` and `System.Linq.ToDictionary<_>()`. What all overloads/methods are being considered that would explain the behavior. – Maslow Jan 06 '17 at 18:32
  • also, I'm well aware of extension methods and how they work. I'm looking for what compilation candidates are being considered as possible that would cause the type inference of the 2nd argument or arguments to the 2nd lambda argument to fail to compile. – Maslow Jan 06 '17 at 18:37