为什么 F# 需要 ToDictionary 的类型占位符?
Why does F# require type placeholders for ToDictionary?
给定
[
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 工作正常,但编译失败并出现错误:Lookup on object of indeterminate type based on information based on information prior to this program point
所以,看起来,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 的 reader 的狂热粉丝,我真的很想知道
什么重载决议(或可能来自不同地方的扩展方法)在这里发生冲突,编译器被混淆了?
简短回答:
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" 前缀定义,即使它在技术上是静态的。它基本上允许您向已定义的 classes 添加方法,而无需重新编译或扩展它们。这称为原型设计。如果您是 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" 的原型(或 class 定义)中。因此,在您的示例中,您实际上具有三个类型参数和三个常规参数,但其中一个未在调用中使用。您所要做的就是声明您所做的键选择器函数和元素选择器函数。这就是为什么它看起来很奇怪。
即使预先知道类型,编译器也会混淆采用元素选择器和比较器的重载。 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,"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 工作正常,但编译失败并出现错误:Lookup on object of indeterminate type based on information based on information prior to this program point
所以,看起来,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 的 reader 的狂热粉丝,我真的很想知道 什么重载决议(或可能来自不同地方的扩展方法)在这里发生冲突,编译器被混淆了?
简短回答:
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" 前缀定义,即使它在技术上是静态的。它基本上允许您向已定义的 classes 添加方法,而无需重新编译或扩展它们。这称为原型设计。如果您是 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" 的原型(或 class 定义)中。因此,在您的示例中,您实际上具有三个类型参数和三个常规参数,但其中一个未在调用中使用。您所要做的就是声明您所做的键选择器函数和元素选择器函数。这就是为什么它看起来很奇怪。
即使预先知道类型,编译器也会混淆采用元素选择器和比较器的重载。 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