Haskell中的孤立实例

时间:2010-06-20 14:17:33

标签: haskell ghc typeclass

使用-Wall选项编译我的Haskell应用程序时,GHC会抱怨孤立的实例,例如:

Publisher.hs:45:9:
    Warning: orphan instance: instance ToSElem Result

类型类ToSElem不是我的,它由HStringTemplate定义。

现在我知道如何解决这个问题(将实例声明移动到声明Result的模块中),我知道why GHC would prefer to avoid orphaned instances,但我仍然相信我的方式更好。我不在乎编译器是否带来不便 - 而不是我。

我想在Publisher模块中声明我的ToSElem实例的原因是因为它是依赖于HStringTemplate的Publisher模块,而不是其他模块。我试图保持关注点的分离,并避免让每个模块都依赖于HStringTemplate。

我认为Haskell类型类的一个优点是,例如与Java的接口相比,它们是开放的而不是封闭的,因此实例不必在与数据类型相同的位置声明。 GHC的建议似乎是忽略了这一点。

所以,我正在寻找的是要么认可我的想法是合理的,要么我有理由忽视/压制这个警告,或者更有说服力地反对按照我的方式做事。

6 个答案:

答案 0 :(得分:86)

我理解你为什么要这样做,但不幸的是,Haskell课程似乎以你所说的方式“开放”可能只是一种错觉。许多人认为这样做的可能性是Haskell规范中的一个错误,原因我将在下面解释。无论如何,如果它实际上不适合于实例,则需要在声明类的模块中声明,或者在声明类型的模块中声明,这可能表示您应该使用{{1}或者你的类型周围的其他一些包装。

需要避免孤立实例的原因比编译器的方便性更深入。从其他答案中可以看出,这个话题颇具争议性。为了平衡讨论,我将解释一个观点,即永远不应该写孤立实例,我认为这是经验丰富的Haskellers中的多数意见。我自己的意见在中间,我将在最后解释。

问题源于这样一个事实:当同一个类和类型存在多个实例声明时,标准Haskell中没有指定要使用的机制。相反,程序会拒绝该程序。

最简单的效果是,你可以拥有一个完美的工作程序,它会突然停止编译,因为其他人在模块的某些远程依赖中做出了改变。

更糟糕的是,由于远程更改,工作程序可能会在运行时崩溃。您可能正在使用一种假定来自某个实例声明的方法,它可以默默地被另一个不同的实例替换,该实例的大小不同会导致程序无法解决地崩溃。

希望保证不会发生这些问题的人必须遵循以下规则:如果任何人,任何地方,曾经为某种类型声明某个类的实例,则不得再次声明其他实例。任何人写的任何程序。当然,有一种使用newtype来声明一个新实例的解决方法,但这至少是一个小小的不便,有时候是一个主要的不便。 所以从这个意义上说,那些故意写孤儿实例的人是相当不礼貌的。

那么应该对这个问题做些什么呢?反孤立实例阵营说GHC警告是一个错误,它需要是一个错误,拒绝任何声明孤立实例的企图。与此同时,我们必须自律,不惜一切代价避免它们。

如您所见,有些人并不那么担心这些潜在的问题。他们实际上鼓励使用孤儿实例作为分离问题的工具,如你所说,并且说人们应该根据具体情况确定没有问题。我被其他人的孤儿实例所困扰的次数让我们确信这种​​态度过于苛刻。

我认为正确的解决方案是为Haskell的导入机制添加扩展,以控制实例的导入。这不会完全解决问题,但它会为保护我们的计划免受世界上已有的孤儿实例的破坏提供一些帮助。然后,随着时间的推移,我可能会相信,在某些有限的情况下,也许孤儿实例可能并不那么糟糕。 (而且这种诱惑是反孤儿阵营中的一些人反对我的提议的原因。)

我从这一切得出的结论是,至少在目前,我强烈建议你避免宣布任何孤儿事件,如果没有其他原因,要考虑别人。使用newtype

答案 1 :(得分:38)

继续并取消此警告!

你的公司很好。 Conal在“TypeCompose”中做到了。 “chp-mtl”和“chp-transformers”做它,“control-monad-exception-mtl”和“control-monad-exception-monadsfd”做它等等。

顺便说一句,你可能已经知道了这一点,但是对于那些不这样做并且在搜索中绊倒你的问题:

{-# OPTIONS_GHC -fno-warn-orphans #-}

修改

我承认伊兹在答案中提到的问题是真正的问题。但是,我认为不使用孤立的实例也是一个问题,我试图选择“最少的所有邪恶”,这是谨慎使用孤儿实例。

我在简短的回答中只使用了感叹号,因为你的问题表明你已经很清楚这些问题。否则,我会不那么热情:)。

有点转移,但我相信在完美的世界中完美的解决方案毫不妥协:

我相信Yitz提到的问题(不知道选择了哪个实例)可以在“整体”编程系统中解决,其中:

  • 您不是原始编辑纯文本文件,而是受环境辅助(例如代码完成仅建议相关类型的内容等)
  • “低级”语言对类型类没有特殊支持,而是明确传递函数表
  • 但是,“更高级别”编程环境以类似于现在如何呈现Haskell的方式显示代码(您通常不会看到传递的函数表),并在它们为您时选择显式类型类显而易见(例如Functor的所有情况只有一个选择),当有几个例子(压缩列表Applicative或list-monad Applicative,First / Last / lift可能是Monoid)时,它允许你选择使用哪个实例。
  • 在任何情况下,即使自动为您挑选实例,环境也很容易让您通过简单的界面(超链接或悬停界面等)查看使用的实例。

回到幻想世界(或者希望未来),现在:我建议当你“真的需要”时仍然使用它们时避免使用孤立实例

答案 2 :(得分:35)

Orphan实例令人讨厌,但在我看来,它们有时是必要的。 我经常组合库,其中类型来自一个库,而类来自另一个库。 当然,不能期望这些库的作者为每种可能的类型和类组合提供实例。所以我必须提供它们,所以它们都是孤儿。

当你需要提供一个实例时,你应该将类型包装成一个新类型的想法是一个具有理论价值的想法,但在许多情况下它太繁琐了;这是那些不以Haskell代码为生的人提出的那种想法。 :)

所以继续并提供孤立实例。它们是无害的。
如果您可以使用孤立实例崩溃ghc那么这是一个错误,应该如此报告。 (ghc有/没有检测到多个实例的bug并不难解决。)

但请注意,将来某个时候其他人可能会添加您已经拥有的某个实例,并且您可能会遇到(编译时)错误。

答案 3 :(得分:17)

在这种情况下,我认为使用孤立实例很好。对我来说一般的经验法则是 - 你可以定义一个实例,如果你“拥有”类型类或者你“拥有”数据类型(或者它的某个组件 - 也就是说,也许一个Maybe MyData的实例也很好,至少有时候)。在这些约束中,您决定放置实例是您自己的业务。

还有一个例外 - 如果你既不拥有类型类或数据类型,而是生成二进制文件而不是库,那么那也没关系。

答案 4 :(得分:5)

(我知道我迟到了,但这可能对其他人有用)

你可以将孤儿实例保存在他们自己的模块中,然后如果有人导入该模块,那是因为他们需要它们,如果它们导致问题,他们可以避免导入它们。

答案 5 :(得分:3)

沿着这些方向,我理解反孤立实例阵营的位置WRT库,但对于可执行目标,不应该孤立实例好吗?