【发布时间】:2017-03-12 03:55:28
【问题描述】:
在 Ocaml 中,是否有一个简单的构造/样式来扩展定义的类型?
说,如果我们有布尔类型
bool2 = True | False
现在我们要将其扩展为 3 值逻辑。在 Ocaml 中,有没有比像这样重新定义 bool2 更优雅的方法:
bool3 = True | False | ThirdOne
【问题讨论】:
在 Ocaml 中,是否有一个简单的构造/样式来扩展定义的类型?
说,如果我们有布尔类型
bool2 = True | False
现在我们要将其扩展为 3 值逻辑。在 Ocaml 中,有没有比像这样重新定义 bool2 更优雅的方法:
bool3 = True | False | ThirdOne
【问题讨论】:
多态变体提供此功能:
type bool2 = [ `True | `False ]
type bool3 = [ bool2 | `Third_one ]
就是这样。
对于多态变体还有另一个有用的快捷方式。在模式匹配中,使用前面带有#的类型名称:
let is_bool2 = function
#bool2 -> true
| `Third_one -> false
应谨慎使用多态变体,因为它们很容易导致令人困惑的错误消息。如果原始类型bool2 无论如何都不是多态变体,则两种类型的并集如下实现。假设bool2是核心类型bool,我们使用经典变体的定义是:
在模式匹配中,编译器将检查是否涵盖了所有情况,而不需要类型注释。它看起来像这样:
将 x 与 |布尔真 -> ... |布尔假 -> ... |第三个-> ...【讨论】:
我建议不要过度使用多态变体。它们在纸面上看起来不错,但更灵活的推理和子类型会随时反击你。当我使用多态变体时,我会尝试确保每次使用都使用精确的约束类型表达式进行注释。
我建议返回并修改您的旧代码,这似乎很自然。如果您在编写代码时考虑到了可扩展性,并且特别避免了 bool2 类型上的 _-patterns,那么编译器将警告您任何假设只有两个构造函数的地方。这个关于类型修改的编译器反馈非常有用,因为它是使您的程序正确的机械帮助。
这种做事方式当然有一些缺点。其中之一是修改类型定义,然后修改每个用例可能不适用于您通常的编译测试实践:如果您在大型代码库上执行此操作,那么在项目编译之前您将有很多重要的事情要做再次干净地(因此可以测试)。你可以将你的修改分成几个补丁到你的版本控制系统,但这意味着一些中间提交的状态不会编译,这不是很令人愉快。另一种可能性是仅更改这些位置以添加运行时故障(| Third_one -> assert false),然后您拥有可编译的代码,并且您可以在应用程序测试期间在运行时纠正这些故障。但我还是觉得静态编译器反馈对代码维护有很好的帮助。
还可以选择将代数数据类型包装在“扩展代数数据类型”type bool3 = New | Old of bool2 中,这在您作为 Martin 回答的评论提供的链接中进行了讨论。这可能是在不中断编译的情况下从一种数据类型转换到另一种数据类型的好方法,但从长远来看,这是很痛苦的,尤其是当您将更多这些扩展堆叠在一起时。
当然,在某些情况下真正需要的是一种通过代码添加而不是代码修改来扩展数据类型的方法,这种方法既静态安全,也更易于编译、运行和测试,而且高效在运行时。这是Expression Problem 的一个实例,它们是各种解决方案,多态变体就是其中之一。但在常见情况下,您不需要仅进行代码添加的额外灵活性,并且不值得相关语言功能的额外复杂性,所以我建议坚持使用普通的旧变体类型,除非它明显是一个巨大的获得做不同的事情。
PS:关于多态变体,以及它们与表达问题的关系,必填论文是 Jacques Garrigue 的Code reuse through polymorphic variants。
【讨论】:
根据手头的任务,您可能还会发现Extensible Variant Types 很有用。更多信息和用例here。
type bool = ..
type bool +=
| True
| False
(* Elsewhere *)
type bool +=
| Third_one
【讨论】: