如果对象类型可扩展,我应该如何指向记录字段?

时间:2019-05-17 13:56:34

标签: object ocaml reason lenses

在Reason中思考一种在对象上定义简单镜头的方法。

我尝试通过以下代码使用可扩展对象(..位于字段列表的前面)

type hasName('a, 't) = {.. name: 't} as 'a;
type lens('s, 'v) = {
  get: 's => 'v,
  set: ('s, 'v) => 's,
};
let nameLens: lens(hasName('a, 't), 't) = {
  get: s => s.name,
  set: (s, v) => {...s, name: v},
}

我得到“找不到记录字段名称”。错误,尽管hasName的类型肯定应该有一个...我在这里做什么错了?

免责声明:我真的是Reason / OCaml的新手,所以我可能会错过一些显而易见的事情。

1 个答案:

答案 0 :(得分:4)

该错误消息不是很好(请轻描淡写),但是如果仔细阅读,它确实指出了问题所在。它说“找不到记录字段名称。”,考虑到您拥有的是对象而不是记录,这并不奇怪。这是一个错误,因为对象成员是通过#而不是.(请参阅the Reason docs on objects)访问的。

据我了解,其原因是OCaml不支持即席多态性(即函数或运算符重载),因此为了使类型推断正常工作,因此需要区分不同类型的操作句法上。我还怀疑它不只是说“花花公子,这是一个对象,而不是记录”的原因是类型系统没有任何记录类型的概念,而是需要特定的记录类型。因此,它尝试首先找到该字段,然后识别与其关联的特定记录类型。如果找不到该字段,则不存在类型错误,因为没有可与之冲突的正确类型。

无论如何,通过使用#而不是.可以轻松解决吸气剂。但是,二传手的问题更大。在这里,您也使用记录更新语法,并且会得到类似的错误,但是不幸的是,对象没有直接的等效项。

使对象面向对象的部分原因是它们的实现是隐藏的。如果您不知道内部内容,则无法从外部复制对象。尽管可以创建符合相同对象类型的其他对象,但是只有知道具体类型后才能这样做。在这种情况下,您不需要。

因此,不变地更新对象的方法是从对象内部的setter方法中进行操作,在那里您需要了解所有知识。不变地更新当前对象的语法为{<name: newName>},仅在方法定义中有效。然后当然,我们还需要将此setter方法添加到hasName类型。

将所有内容放在一起,我们得到以下结果:

type hasName('a, 't) =
  {
    ..
    name: 't,
    setName: 't => 'a,
  } as 'a;

type lens('s, 'v) = {
  get: 's => 'v,
  set: ('s, 'v) => 's,
};

let nameLens: lens(hasName('a, 't), 't) = {
  get: s => s#name,
  set: (s, v) => s#setName(v),
};

let obj = {
  val name =
    "myName";

  pub name =
    name;

  pub setName = newName =>
    {<name: newName>}
};

nameLens.set(obj, "newName")
  |> nameLens.get
  |> print_endline