在 F# 中使用语句进行跨类型记录?

Cross-type record with statements in F#?

以下代码无法编译:

type Person = 
  {
    FirstName : string
    LastName : string
  }

type Employee = 
  {
    FirstName : string
    LastName : string
    Salary : int
  }

let p : Person = 
  {
    FirstName = "Steve"
    LastName = "Jobs"
  }

let e : Employee = 
  {
    p with Salary = 1
  }

一个解决方法是写一个助手,也许像这样:

let toEmployee (p : Person) : Employee = 
  {
    FirstName = p.FirstName
    LastName = p.LastName
    Salary = Unchecked.defaultof<_>
  }

let e : Employee = 
  {
    toEmployee p with Salary = 1
  }

但这有点乏味。

F# 是否提供了更好的方法?

这在JavaScript中很常见,例如:

const e = {
  ...p,
  salary: 1,
};

作为静态和名义类型的语言,这在 F# 中是不可能的,这是有意设计的。

假设您确实拥有像 JS 这样的功能和一个值 let e = {...p; Salary = 1}。然后假设您在 Person 上重命名了一个字段。 e 的类型应该推断为什么?错误消息应该说什么?这个问题没有简单的答案,在更大的代码库中它可能会变得非常复杂且难以快速理解。

最好手动写出新记录。它有点冗长,但它符合 F# 的一般主题,即在某些维度上更明确,因此您可以不那么具体地注释类型(允许更多类型推断)。如果你对两者都非常含蓄,那么它会造成复杂性的爆炸式增长。


另一个可能对此有帮助的选项是更改您的模型以将重复的字段放入单独的类型中:

type Person = 
  { FirstName : string
    LastName : string }

type Employee = 
  { Person : Person
    Salary : int }

let p = 
  { FirstName = "Steve"
    LastName = "Jobs" }

let e = { Person = p; Salary = 1 }

在 F# 中,您对类型建模的方式非常重要,并且会对使用它的所有代码产生连锁反应。因此,如果您的代码看起来很不稳定,请考虑是否可以改进您的类型。