为什么在更新记录时F#不推断记录类型?

时间:2015-03-05 21:02:03

标签: f# 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. 为什么不根据参数类型推断并返回相应的记录类型?
  2. 我能做些什么让这项工作?

3 个答案:

答案 0 :(得分:3)

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

F#规范的第6.3.6节

  

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

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

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

答案 1 :(得分:1)

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

答案 2 :(得分:1)

使这项工作所需的内联魔法基于重载决策和静态解析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'

这并非真正用于实际用途。听取建议,并探讨其他方法是否更适合您的问题。