给出
[
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)
如果我在<_,_,_>
之后没有明确使用ToDictionary
,则无法编译 Intellisense工作正常,但编译失败并出现错误:根据此程序点之前的信息查找不确定类型的对象 所以,似乎Intellisense知道如何解决方法调用。
这似乎是一个线索
|> fun x -> x.ToDictionary<_,_>((fun x -> x.Key), fun x -> x.Value)
失败
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)
按预期工作
let vMap (item:KeyValuePair<_,_>) = item.Value
x.ToDictionary((fun x -> x.Key), vMap)
我已经复制了FSI和LinqPad中的行为。
作为Eric Lippert的狂热读者,我真的很想知道 什么重载决议,(或可能来自不同地方的扩展方法)在这里是冲突的,编译器被它迷惑了?
答案 0 :(得分:2)
即使前面已知类型,编译器也会在带有元素选择器和比较器的重载之间混淆。 lambda编译为FSharpFunc
而不是C#中的标准委托类型,如Action
或Func
,并且问题确实会从一个转换为另一个。为了使其有效,您可以:
为违规的Func提供类型注释
fun x -> x.ToDictionary((fun pair -> pair.Key), (fun (pair : KeyValuePair<_, _>) -> pair.Value)) //compiles
或将参数命名为提示
fun x -> x.ToDictionary((fun pair -> pair.Key), elementSelector = (fun (pair) -> pair.Value))
或强制它选择3参数版本:
x.ToLookup((fun pair -> pair.Key), (fun (pair) -> pair.Value), EqualityComparer.Default)
除了
在您的示例中,
let vMap (item:KeyValuePair<_,_>) = item.Value
x.ToDictionary((fun x -> x.Key), vMap)
您明确需要注释vMap
,因为编译器无法在没有其他传递的情况下找出该属性存在的类型。例如,
List.map (fun x -> x.Length) ["one"; "two"] // this fails to compile
这是管道运算符如此有用的原因之一,因为它允许您避免类型注释:
["one"; "two"] |> List.map (fun x -> x.Length) // works
List.map (fun (x:string) -> x.Length) ["one"; "two"] //also works
答案 1 :(得分:1)
答案简短:
extension method ToDictionary的定义如下:
static member ToDictionary<'TSource,_,_>(source,_,_)
但被称为:
source.ToDictionary<'TSource,_,_>(_,_)
答案很长:
这是您从msdn调用的函数的F#类型签名。
static member ToDictionary<'TSource, 'TKey, 'TElement> :
source:IEnumerable<'TSource> *
keySelector:Func<'TSource, 'TKey> *
elementSelector:Func<'TSource, 'TElement> -> Dictionary<'TKey, 'TElement>
但我只指定了两个常规参数:keySelector和elementSelector。为什么这有源参数?!
源参数实际上没有放在括号中,而是通过说x.ToDictionary传入,其中x是源参数。这实际上是type extension的一个例子。这些方法在F#等函数式编程语言中非常自然,但在面向对象语言(如C#)中更为常见,所以如果你来自C#世界,它将会非常混乱。无论如何,如果我们查看C#标题,可以更容易理解发生了什么:
public static Dictionary<TKey, TElement> ToDictionary<TSource, TKey, TElement>(
this IEnumerable<TSource> source,
Func<TSource, TKey> keySelector,
Func<TSource, TElement> elementSelector
)
因此该方法在第一个参数上定义了“this”前缀,即使它在技术上是静态的。它基本上允许您向已定义的类添加方法,而无需重新编译或扩展它们。这称为原型设计。如果你是一名C#程序员,这种情况很少见,但是像python和javascript这样的语言会让你意识到这一点。以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']
add_trick方法定义为self作为第一个参数,但该函数被称为d.add_trick('roll over')。 F#实际上也是自然地做到了这一点,但是模仿了调用函数的方式。当你声明:
member x.doSomething() = ...
或
member this.doSomething() = ...
在这里,您将函数doSomething添加到“x”/“this”的原型(或类定义)中。因此,在您的示例中,您实际上有三个类型参数和三个常规参数,但其中一个未在调用中使用。您剩下的就是声明键选择器功能和元素选择器功能。这就是为什么它看起来很奇怪。