【问题标题】:Why doesn't F# infer the record type when updating a record?为什么 F# 在更新记录时不推断记录类型?
【发布时间】:2015-03-05 21:02:02
【问题描述】:

此示例代码:

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. 为什么这不只是根据参数类型推断并返回适当的记录类型?
  2. 我能做些什么来完成这项工作吗?

【问题讨论】:

  • 记录并没有真正做继承。使用一些内联魔法或许可以做到这一点,但您可能需要重新考虑您的设计。

标签: f# record


【解决方案1】:

函数modifyX 不正确。您不能在定义中使用术语 X 并让 X 引用不同的字段。

F# 规范的第 6.3.6 节

每个字段标签必须解析为单个记录类型 R 中的字段 Fi,其所有字段都可以访问。

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

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

【讨论】:

    【解决方案2】:

    完成这项工作所需的内联魔法是基于重载解析和静态解析member constraints。定义为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'
    

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

    【讨论】:

    • 啊,那是我懒得做的!
    【解决方案3】:

    问题在于编译器根据使用情况推断modifyX 的类型。我的理解是这是自下而上进行的,所以推断类型为val modifyX : newX:string -> record:recordB -> recordB。当然,当尝试将其与recordA 类型的记录一起使用时,这会导致类型错误。警告告诉您,尽管它选择了一种类型,但还有另一种具有相同字段的类型,因此编译器所能做的就是最好地猜测您的意思是哪种类型。使用内联函数可能可以实现您想要做的事情,但我不确定它是如何临时工作的。

    【讨论】:

      猜你喜欢
      • 2022-08-10
      • 2017-07-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-01-03
      • 1970-01-01
      相关资源
      最近更新 更多