可扩展记录是 Elm 最令人惊奇的功能之一,但自 v0.16 以来,添加和删除字段不再可用。这让我处于尴尬的境地。
考虑一个例子。我想给一个随机的东西命名t,可扩展记录为我提供了一个完美的工具:
type alias Named t = { t | name: String }
Run Code Online (Sandbox Code Playgroud)
“好的,”编译器说。现在我需要一个构造函数,即一个为事物配备指定名称的函数:
equip : String -> t -> Named t
equip name thing = { thing | name = name } -- Oops! Type mismatch
Run Code Online (Sandbox Code Playgroud)
编译失败,因为{ thing | name = ... }语法假定thing是带有name字段的记录,但类型系统不能保证这一点。事实上,Named t我试图表达一些相反的东西:t应该是一个没有自己name字段的记录类型,并且该函数将此字段添加到记录中。无论如何,字段添加是实现equip功能所必需的。
因此,equip以多态方式编写似乎是不可能的,但这可能不是什么大问题。毕竟,任何时候我要给一些具体的东西命名时,我都可以手动完成。更糟糕的是,逆函数extract : Named t -> t(擦除命名事物的名称)需要字段删除机制,因此也无法实现:
extract : Named t -> t
extract thing = thing -- Error: No implicit upcast
Run Code Online (Sandbox Code Playgroud)
这将是非常重要的功能,因为我有大量的例程接受老式的未命名事物,我需要一种方法来将它们用于命名事物。当然,对这些功能进行大规模重构是不合格的解决方案。
最后,在经过这么长的介绍之后,让我提出我的问题:
现代 Elm 是否提供了一些替代旧的已弃用字段添加/删除语法?
如果没有,是否有类似equip及extract以上的内置功能?对于每个自定义可扩展记录类型,我都希望有一个多态分析器(一个提取其基本部分的函数)和一个多态构造函数(一个将基本部分与加法相结合并生成记录的函数)。
(1) 和 (2) 的否定答案将迫使我以Named t更传统的方式实施:
type Named t = Named String t
Run Code Online (Sandbox Code Playgroud)
在这种情况下,我无法理解可扩展记录的目的。是否有积极的用例,可扩展记录在其中发挥关键作用的场景?
类型{ t | name : String }表示具有name字段的记录。它不扩展t类型,而是扩展编译器关于t自身的知识。
所以实际上的类型equip是String -> { t | name : String } -> { t | name : String }.
更重要的是,正如您所注意到的,Elm 不再支持向记录添加字段,因此即使类型系统允许您想要的,您仍然无法做到。{ thing | name = name }语法仅支持更新类型的记录{ t | name : String }。
同样,不支持从记录中删除字段。
如果您确实需要可以添加或删除字段的类型,则可以使用Dict. 其他选项是手动编写转换器,或创建和使用代码生成器(这是 JSON 解码样板的推荐解决方案一段时间)。
关于可扩展记录,Elm 不再真正支持“可扩展”部分——唯一剩下的部分是{ t | name : u } -> u投影,所以也许它应该被称为作用域记录。Elm文档本身承认可扩展性目前不是很有用。