在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的新手,所以我可能会错过一些显而易见的事情。
答案 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