Haskell中的孤立实例

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

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

typesToSElem不是我的,它是由HStringTemplate定义的。

现在我知道如何解决这个问题(将实例声明移动到声明了Result的模块中),我知道为什么GHC宁愿避免孤立的实例 ,但我仍然相信我的方式更好。 我不在乎编译器是否不方便 – 而不是我。

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

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

所以,我所要找的是要么证实我的思想是正确的,要么忽略/压制这个警告是合理的,要么是一个更有说服力的论点来反对我的做法。

我明白你为什么要这样做,但不幸的是,Haskell类似乎像你所说的那样是“开放的”。 许多人认为这样做的可能性是Haskell规范中的一个错误,我将在下面解释。 无论如何,如果实际上不适合实例,则需要在声明该类的模块中或在声明该types的模块中声明,这可能表示您应该使用新types或其他包装你的types。

孤儿实例需要避免的原因比编译器的便利性要深入得多。 这个话题是相当有争议的,你可以从其他答案中看到。 为了平衡讨论,我将解释一个观点,即永远不应该写孤儿实例,我认为这个实例是经验丰富的哈斯克勒家族中的大多数人。 我自己的意见是在中间的某个地方,我会在最后解释。

这个问题源于这样一个事实:当同一个类和types存在多个实例声明时,标准Haskell没有指定使用哪一个的机制。 相反,程序被编译器拒绝。

最简单的效果就是你可以拥有一个完美的工作程序,这个程序会突然停止编译,因为别人在你的模块的一些远离依赖的情况下所做的改变。

更糟糕的是,由于远程更改,工作程序可能会在运行时崩溃 。 你可能正在使用一种你假设来自某个实例声明的方法,它可以默默地被一个不同的实例替代,这个实例只是不同的,会导致程序开始莫名其妙地崩溃。

那些想要保证这些问题不会发生在他们身上的人必须遵循这样的规则:如果任何人在任何地方曾经为某种types声明某个类的实例,那么在写入的任何程序中都不能再声明其他实例由任何人。 当然,使用新types来声明一个新的实例是一个解决方法,但是这总是起到一点小的不便,有时也是一个很大的麻烦。 所以从这个意义上来说,那些故意写孤儿实例的人是相当不礼貌的。

那么这个问题该怎么办? 反孤儿实例阵营说,GHC警告是一个错误,它必须是一个拒绝任何尝试声明一个孤儿实例的错误。 同时,要自律,不惜一切代价避免。

正如你所看到的那样,有些人不那么担心这些潜在的问题。 他们实际上鼓励将孤儿实例作为分离问题的工具,正如你所build议的那样,并且应该在个案的基础上确保没有问题。 我被其他人的孤儿困扰了很多次,以为这种态度太过于激进了。

我认为正确的解决scheme是添加一个扩展到Haskell的导入机制,将控制实例的导入。 这并不能完全解决问题,但对于保护我们的计划免受世界上已存在的孤儿事件的损害将有所帮助。 然后,随着时间的推移,我可能会确信,在某些有限的情况下,也许孤儿实例可能并不那么糟糕。 (这个诱惑就是有些反对孤儿阵营反对我的build议的原因。)

我所得出的结论是,至less在目前的情况下,我强烈build议你避免宣布任何孤儿实例,如果没有其他原因,要体谅别人。 使用新types。

继续并压制这个警告!

你是一个很好的公司。 Conal在“TypeCompose”中做了这个。 “chp-mtl”和“chp-transformers”这样做,“control-monad-exception-mtl”和“control-monad-exception-monadsfd”等等。

顺便说一句,你可能已经知道这一点,但对于那些没有和绊倒你的问题在search:

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

编辑:

我承认Yitz在他的回答中提到的问题是真正的问题。 不过,我并不认为孤立的实例也是一个问题,我试图挑选“最不重要的罪恶”,这是审慎使用孤立实例的原因。

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

有点转移,但我相信是在一个完美的世界完美的解决scheme没有妥协:

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

  • 你不是简单地编辑纯粹的文本文件,而是相当于环境的帮助(例如代码完成只提供相关types的东西等)
  • “低级”语言对types没有特别的支持,而是function表被明确地传递
  • 但是,“更高层次”的编程环境以类似的方式显示代码,Haskell现在是如何呈现的(通常不会看到传递的函数表),并且在显式的时候为你select显式的types举例来说Functor的所有情况只有一个select),当有几个例子(压缩列表Applicative或列表Monad Applicative,First / Last /提升可能Monoid)时,它允许您select使用哪个实例。
  • 在任何情况下,即使自动为您挑选实例,环境也可以轻松地让您查看使用哪个实例,使用简单的界面(超链接或hover界面或其他)

从幻想世界(或者希望未来)回来,现在我build议在“真正需要”的时候尽量避免使用孤立的实例

孤儿实例是一个麻烦,但在我看来,他们有时是必要的。 我经常把来自一个图书馆的types和来自另一个图书馆的图书馆结合起来。 当然,这些图书馆的作者不能期望为每种可能的types和类别的组合提供实例。 所以我必须提供他们,所以他们是孤儿。

当你需要提供一个实例时,你应该把这个types换成一个新types,这个想法是有理论价值的,但是在很多情况下它太乏味了, 这是那些不写Haskell代码的人提出的一种想法。 🙂

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

但是请注意,将来有一段时间,别人可能会添加一些已经存在的实例,并且可能会出现(编译时)错误。

在这种情况下,我认为使用孤立实例很好。 对我来说,一般的经验法则是 – 你可以定义一个实例,如果你“拥有”这个typestypes,或者你拥有了这个数据types(或者它的某个组件 – 也就是说,也许MyData的一个实例也是好的,至less有时)。 在这些限制之内,你决定放置实例的地方是你自己的事情。

还有一个例外 – 如果你既没有typestypes,也没有数据types,但是生成一个二进制文件而不是一个库,那也没关系。

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

您可以将孤立实例保留在自己的模块中,然后如果有人导入该模块,那么特别是因为他们需要它们,并且如果导致问题,可以避免导入它们。

沿着这些路线,我了解反孤儿实例营的位置WRT库,但对于可执行目标不应该孤儿实例罚款?