为什么 F# 在更新记录时不推断记录类型?
Why doesn't F# infer the record type when updating a record?
此示例代码:
type recordA = { X: string; }
type recordB = { X: string; }
let modifyX newX record = { record with X = newX }
let modifiedRecordA = {recordA.X = "X"} |> modifyX "X2"
let modifiedRecordB = {recordB.X = "X"} |> modifyX "X2"
结果:
let modifyX newX record = { record with X = newX }
--------------------------^^^^^^^^^^^^^^^^^^^^^^^^
stdin(4,27): warning FS0667: The field labels and expected type of this record expression or pattern do not uniquely determine a corresponding record type
let modifiedRecordA = {recordA.X = "X"} |> modifyX "X2"
-------------------------------------------^^^^^^^^^^^^
stdin(6,44): error FS0001: Type mismatch. Expecting a
recordA -> 'a
but given a
recordB -> recordB
The type 'recordA' does not match the type 'recordB'
我的期望是 modifiedRecordA 最终等效于 { recordA.X = "X2" },而 modifiedRecordB 最终等效于 { recordB.X = "X2" },但事实并非如此好像是这样的。
- 为什么这不只是根据参数类型推断 return 适当的记录类型?
- 我可以做些什么来完成这项工作吗?
问题在于编译器正在根据用法推断 modifyX
的类型。我的理解是,这是自下而上的,所以类型被推断为val modifyX : newX:string -> record:recordB -> recordB
。当然,当尝试将其与 recordA
类型的记录一起使用时,这会导致类型错误。该警告告诉您,尽管它选择了一种类型,但还有另一种具有相同字段的类型,因此编译器所能做的就是尽可能地猜测您指的是哪种类型。使用内联函数可能会实现您想要做的事情,但我不确定它是如何工作的。
函数modifyX
不正确。您不能在定义中使用术语 X
,并让 X
指代不同的字段。
F# 规范第 6.3.6 节
Each field label must resolve to a field Fi in a single record type R, all of whose fields are accessible.
通过将 recordA
和 recordB
传递给 modifyX
,X
不是唯一确定为单一类型的字段。
您真正想要的可能是通过接口继承的多态 属性 成员,而不是一组具有共同成员名称的记录类型。
完成此工作所需的内联魔术基于重载解析与静态解析相结合member constraints. Overloads defined as operators避免需要拼写明确的约束。
type Foo = Foo with
static member ($) (Foo, record) = fun newX -> { record with recordA.X = newX }
static member ($) (Foo, record) = fun newX -> { record with recordB.X = newX }
let inline modifyX newX record = (Foo $ record) newX
let modifiedRecordA = {recordA.X = "X"} |> modifyX "X2"
let modifiedRecordB = {recordB.X = "X"} |> modifyX "X2"
不编译不存在重载的传递类型的构造。
type recordC = { X: string; }
let modifiedRecordC = {recordC.X = "X"} |> modifyX "X2"
// Error No overloads match for method 'op_Dollar' ...
// Possible overload ... The type 'recordC' is not compatible with the type 'recordB'
// Possible overload ... The type 'recordC' is not compatible with the type 'recordA'
这并不是真正为实际使用而设计的。听取建议并探索其他方法是否更适合您的问题。
此示例代码:
type recordA = { X: string; }
type recordB = { X: string; }
let modifyX newX record = { record with X = newX }
let modifiedRecordA = {recordA.X = "X"} |> modifyX "X2"
let modifiedRecordB = {recordB.X = "X"} |> modifyX "X2"
结果:
let modifyX newX record = { record with X = newX }
--------------------------^^^^^^^^^^^^^^^^^^^^^^^^
stdin(4,27): warning FS0667: The field labels and expected type of this record expression or pattern do not uniquely determine a corresponding record type
let modifiedRecordA = {recordA.X = "X"} |> modifyX "X2"
-------------------------------------------^^^^^^^^^^^^
stdin(6,44): error FS0001: Type mismatch. Expecting a
recordA -> 'a
but given a
recordB -> recordB
The type 'recordA' does not match the type 'recordB'
我的期望是 modifiedRecordA 最终等效于 { recordA.X = "X2" },而 modifiedRecordB 最终等效于 { recordB.X = "X2" },但事实并非如此好像是这样的。
- 为什么这不只是根据参数类型推断 return 适当的记录类型?
- 我可以做些什么来完成这项工作吗?
问题在于编译器正在根据用法推断 modifyX
的类型。我的理解是,这是自下而上的,所以类型被推断为val modifyX : newX:string -> record:recordB -> recordB
。当然,当尝试将其与 recordA
类型的记录一起使用时,这会导致类型错误。该警告告诉您,尽管它选择了一种类型,但还有另一种具有相同字段的类型,因此编译器所能做的就是尽可能地猜测您指的是哪种类型。使用内联函数可能会实现您想要做的事情,但我不确定它是如何工作的。
函数modifyX
不正确。您不能在定义中使用术语 X
,并让 X
指代不同的字段。
F# 规范第 6.3.6 节
Each field label must resolve to a field Fi in a single record type R, all of whose fields are accessible.
通过将 recordA
和 recordB
传递给 modifyX
,X
不是唯一确定为单一类型的字段。
您真正想要的可能是通过接口继承的多态 属性 成员,而不是一组具有共同成员名称的记录类型。
完成此工作所需的内联魔术基于重载解析与静态解析相结合member constraints. Overloads defined as operators避免需要拼写明确的约束。
type Foo = Foo with
static member ($) (Foo, record) = fun newX -> { record with recordA.X = newX }
static member ($) (Foo, record) = fun newX -> { record with recordB.X = newX }
let inline modifyX newX record = (Foo $ record) newX
let modifiedRecordA = {recordA.X = "X"} |> modifyX "X2"
let modifiedRecordB = {recordB.X = "X"} |> modifyX "X2"
不编译不存在重载的传递类型的构造。
type recordC = { X: string; }
let modifiedRecordC = {recordC.X = "X"} |> modifyX "X2"
// Error No overloads match for method 'op_Dollar' ...
// Possible overload ... The type 'recordC' is not compatible with the type 'recordB'
// Possible overload ... The type 'recordC' is not compatible with the type 'recordA'
这并不是真正为实际使用而设计的。听取建议并探索其他方法是否更适合您的问题。