Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 624
Maslow Avatar asked Jan 06 '17 15:01

Maslow


People also ask

What does f do in math?

A function is most often denoted by letters such as f, g and h, and the value of a function f at an element x of its domain is denoted by f(x); the numerical value resulting from the function evaluation at a particular input value is denoted by replacing x with this value; for example, the value of f at x = 4 is ...

What does f with an apostrophe mean?

This formula, or new function, is called the derivative of the original function. When we find it we say that we are differentiating the function. The derivative of f(x) is written using an apostrophe after the f. The notation is f´(x) or y´ The notation dy/dx is also commonly used.

Why does f XC shift right?

Horizontal Shift If k is positive, then the graph will shift left. If k is negative, then the graph will shift right.

Does Y equal FX?

Remember: The notation "f (x)" is exactly the same thing as "y". You can even label the y-axis on your graphs with "f (x)", if you feel like it.


2 Answers

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
like image 131
Asti Avatar answered Sep 30 '22 06:09

Asti


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.

like image 31
user3685285 Avatar answered Sep 30 '22 05:09

user3685285