为什么 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" },但事实并非如此好像是这样的。

  1. 为什么这不只是根据参数类型推断 return 适当的记录类型?
  2. 我可以做些什么来完成这项工作吗?

问题在于编译器正在根据用法推断 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.

通过将 recordArecordB 传递给 modifyXX 不是唯一确定为单一类型的字段。

您真正想要的可能是通过接口继承的多态 属性 成员,而不是一组具有共同成员名称的记录类型。

完成此工作所需的内联魔术基于重载解析与静态解析相结合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'

这并不是真正为实际使用而设计的。听取建议并探索其他方法是否更适合您的问题。