我理解您为什么要这样做,但不幸的是,Haskell 类似乎以您所说的方式“开放”可能只是一种错觉。许多人认为这样做的可能性是 Haskell 规范中的一个错误,原因我将在下面解释。无论如何,如果它确实不适合您需要在声明类的模块或声明类型的模块中声明的实例,这可能表明您应该使用一个newtype
或其他包装器围绕你的类型。
需要避免孤儿实例的原因远不止编译器的便利性。从其他答案中可以看出,这个话题颇具争议。为了平衡讨论,我将解释一个观点,即永远不应该编写孤儿实例,我认为这是有经验的 Haskellers 中的大多数观点。我自己的观点介于两者之间,我将在最后解释。
问题源于这样一个事实,即当同一类和类型存在多个实例声明时,标准 Haskell 中没有指定使用哪一个的机制。相反,该程序被编译器拒绝。
最简单的效果是,您可能拥有一个完美运行的程序,但由于其他人对您的模块的某个遥远的依赖项所做的更改而突然停止编译。
更糟糕的是,一个正在运行的程序有可能因为一个遥远的变化而在运行时开始崩溃。您可能正在使用您假设来自某个实例声明的方法,并且它可以被一个不同的实例悄悄地替换,该实例的差异足以导致您的程序开始莫名其妙地崩溃。
想要保证这些问题永远不会发生在他们身上的人必须遵循这样的规则:如果任何人在任何地方曾经为某种类型声明过某个类的实例,那么在任何编写的程序中都不能再声明其他实例由任何人。当然,也有使用 anewtype
来声明一个新实例的解决方法,但这总是至少是一个小的不便,有时是一个主要的不便。所以从这个意义上说,那些故意写孤儿实例的人是相当不礼貌的。
那么这个问题应该怎么办呢?反孤儿实例阵营说 GHC 警告是一个错误,它需要是一个错误,拒绝任何声明孤儿实例的尝试。与此同时,我们必须自律,不惜一切代价避免它们。
如您所见,有些人并不那么担心那些潜在的问题。正如您所建议的,他们实际上鼓励使用孤立实例作为分离关注点的工具,并说应该根据具体情况确保没有问题。其他人的孤儿实例给我带来了足够多的不便,以至于我确信这种态度太漫不经心了。
我认为正确的解决方案是为 Haskell 的导入机制添加一个扩展,以控制实例的导入。这并不能完全解决问题,但它有助于保护我们的程序免受世界上已经存在的孤儿实例的损害。然后,随着时间的推移,我可能会相信在某些有限的情况下,孤儿实例可能不会那么糟糕。(而正是这个诱惑,就是反孤例阵营中的一些人反对我的提议的原因。)
我从这一切中得出的结论是,至少在目前,我强烈建议您避免声明任何孤立实例,如果没有其他原因,请为他人着想。使用newtype
.