f# - 我不明白这个映射元组键编译错误,在 F#
问题描述
这是一个函数:
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
)
它按预期编译。
让我们关注两行:
let key = (x.Instrument, x.Side)
match brain.Positions.TryGetValue key with
brain.Positions是 Map<Instrument * Side, PositionData> 类型
如果我将第二行修改为:
match brain.Positions.TryGetValue (x.Instrument, x.Side) with
那么代码将无法编译,并出现错误:
[FS0001] 此表达式应具有“Instrument * Side”类型,
但此处具有“Instrument”类型
但:
match brain.Positions.TryGetValue ((x.Instrument, x.Side)) with
将编译...
这是为什么?
解决方案
这是由于方法调用语法。
TryGetValue
不是函数,而是方法。一件非常不同的事情,而且总的来说更糟糕。并受制于一些特殊的句法规则。
你看,这个方法实际上有两个参数,而不是一个。正如您所料,第一个参数是一个键。第二个参数在 C# 中被称为out
参数 - 即第二个返回值。它最初打算在 C# 中调用的方式是这样的:
Dictionary<int, string> map = ...
string val;
if (map.TryGetValue(42, out val)) { ... }
的“常规”返回值TryGetValue
是一个布尔值,表示是否找到了密钥。而这里表示的“额外”返回值out val
是键对应的值。
当然,这是非常尴尬的,但它并没有阻止早期的 .NET 库非常广泛地使用这种模式。因此 F# 为这种模式提供了特殊的语法糖:如果您只传递一个参数,那么结果将变成一个由“实际”返回值和out
参数组成的元组。这是您在代码中匹配的内容。
但当然,F# 不能阻止您完全按照设计使用该方法,因此您也可以自由传递两个参数 - 第一个是键,第二个是byref
单元格(相当于 F# 的out
)。
这就是这与方法调用语法发生冲突的地方。您会看到,在 .NET 中,所有方法都是非柯里化的,这意味着它们的参数都是有效的元组。所以当你调用一个方法时,你传递的是一个元组。
这就是在这种情况下发生的情况:只要添加括号,编译器就会将其解释为尝试使用元组参数调用 .NET 方法:
brain.Positions.TryGetValue (x.Instrument, x.Side)
^ ^
first arg |
second arg
在这种情况下,它期望第一个参数是 type Instrument * Side
,但你显然只是传递了一个Instrument
. 这正是错误消息告诉您的内容:“预期有'Instrument * Side'类型,但这里有'Instrument'类型”。
但是当您添加第二对括号时,含义会发生变化:现在外部括号被解释为“方法调用语法”,而内部括号被解释为“表示元组”。所以现在编译器将整个事情解释为一个参数,并且一切都像以前一样工作。
顺便说一句,以下内容也将起作用:
brain.Positions.TryGetValue <| (x.Instrument, x.Side)
这是可行的,因为现在它不再是“方法调用”语法,因为括号不会立即跟随方法名称。
但更好的解决方案是,一如既往,不要使用方法,而是使用函数!
.TryGetValue
在此特定示例中,使用代替Map.tryFind
。这是同一件事,但具有适当的功能形式。不是方法。一个函数。
brain.Positions |> Map.tryFind (x.Instrument, x.Side)
问:但是为什么这种令人困惑的方法甚至存在呢?
兼容性。与尴尬和荒谬的事情一样,答案是:兼容性。
标准的 .NET 库具有此接口System.Collections.Generic.IDictionary
,并且在该接口上TryGetValue
定义了方法。并且每个类字典类型,包括Map
,通常都期望实现该接口。所以给你。
推荐阅读
- javascript - 我需要使用 angulat 10 或 javascript 在新窗口中打开 PDF 文件。但我还需要在打开文件之前更改文件名
- python-3.x - 从命令行作为独立客户端运行python程序(使用pip打包和安装)
- flutter - 分栏符对齐
- linux - Postfix 队列后内容过滤器完整示例
- reactjs - 在控制台中出现错误 Uncaught ReferenceError: arguments is not defined after upgrade the webpack 4 to webpack 5
- vue.js - 重新加载后Vuex状态不包含
- mysql - Redshift 表使用 75GB 磁盘,而 Mysql 上的等效表使用 40GB
- reactjs - 未捕获(承诺中)错误:网络错误:无法获取
- vue.js - vuejs中的条件显示
- cuda - 有没有办法将 128 位从内存直接加载到寄存器?