这是一个函数:
let newPositions : PositionData list =
positions
|> List.filter (fun x ->
let key = (x.Instrument, x.Side)
match brain.Positions.TryGetValue key with
| false, _ ->
// if we don't know the position, it's new
true
| true, p when x.UpdateTime > p.UpdateTime ->
// it's newer than the version we have, it's new
true
| _ ->
false
)
Run Code Online (Sandbox Code Playgroud)
它按预期编译。
让我们关注两行:
let key = (x.Instrument, x.Side)
match brain.Positions.TryGetValue key with
Run Code Online (Sandbox Code Playgroud)
Brain.Positions是 Map<Instrument * Side, PositionData> 类型
如果我将第二行修改为:
match brain.Positions.TryGetValue (x.Instrument, x.Side) with
Run Code Online (Sandbox Code Playgroud)
那么代码将无法编译,并出现错误:
[FS0001] 该表达式的类型应为“Instrument * Side”
,但此处的类型为“Instrument”
但:
match brain.Positions.TryGetValue ((x.Instrument, x.Side)) with
Run Code Online (Sandbox Code Playgroud)
将编译...
这是为什么?
这是由于方法调用语法造成的。
TryGetValue不是一个函数,而是一个方法。这是一件非常不同的事情,而且总的来说是一件更糟糕的事情。并遵守一些特殊的语法规则。
你看,这个方法实际上有两个参数,而不是一个。正如您所期望的,第一个参数是键。第二个参数是 C# 中所谓的out参数,即第二个返回值。它最初在 C# 中的调用方式是这样的:
Dictionary<int, string> map = ...
string val;
if (map.TryGetValue(42, out val)) { ... }
Run Code Online (Sandbox Code Playgroud)
的“常规”返回值TryGetValue是一个布尔值,表示是否找到了该密钥。而这里表示的“额外”返回值out val,就是与该键对应的值。
当然,这是极其尴尬的,但它并没有阻止早期的 .NET 库非常广泛地使用这种模式。因此,F# 对这种模式有特殊的语法糖:如果只传递一个参数,那么结果将成为一个由“实际”返回值和参数组成的元组out。这就是您在代码中要匹配的内容。
当然,F# 不能阻止您完全按照设计使用该方法,因此您也可以自由传递两个参数 - 第一个参数是键,第二个参数是单元格byref(在 F# 中相当于out)。
这就是与方法调用语法发生冲突的地方。您会看到,在 .NET 中,所有方法都是非柯里化的,这意味着它们的参数都是有效的元组。因此,当您调用方法时,您正在传递一个元组。
这就是本例中发生的情况:只要添加括号,编译器就会将其解释为尝试使用元组参数调用 .NET 方法:
brain.Positions.TryGetValue (x.Instrument, x.Side)
^ ^
first arg |
second arg
Run Code Online (Sandbox Code Playgroud)
在这种情况下,它期望第一个参数的类型为Instrument * Side,但您显然只传递了一个Instrument. 这正是错误消息告诉您的内容:“预期类型为‘Instrument * Side’,但此处类型为‘Instrument’ ”。
但是,当您添加第二对括号时,含义会发生变化:现在外部括号被解释为“方法调用语法”,内部括号被解释为“表示元组”。所以现在编译器将整个事情解释为一个参数,并且一切都像以前一样工作。
顺便说一句,以下内容也将起作用:
brain.Positions.TryGetValue <| (x.Instrument, x.Side)
Run Code Online (Sandbox Code Playgroud)
这是有效的,因为现在它不再是“方法调用”语法,因为括号不会立即跟在方法名称后面。
但更好的解决方案是一如既往,不要使用方法,而是使用函数!
在此特定示例中,.TryGetValue使用代替Map.tryFind。这是同样的事情,但是以正确的函数形式。不是方法。一个函数。
brain.Positions |> Map.tryFind (x.Instrument, x.Side)
Run Code Online (Sandbox Code Playgroud)
问:但是为什么会存在这种令人困惑的方法呢?
兼容性。对于尴尬和无意义的事情,答案总是:兼容性。
标准 .NET 库具有此接口,并且在该接口上定义了System.Collections.Generic.IDictionary该方法。TryGetValue每个类似字典的类型(包括Map)通常都应该实现该接口。那么就到这里吧。