Haskell中的孤立实例

Dan*_*yer 82 haskell typeclass ghc

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

Publisher.hs:45:9:
    Warning: orphan instance: instance ToSElem Result
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

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

Yit*_*itz 89

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

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

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

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

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

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

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

如您所见,有些人并不那么担心这些潜在的问题.他们实际上鼓励使用孤儿实例作为分离问题的工具,正如您所建议的那样,并且应该根据具体情况确保没有问题.我被其他人的孤儿实例给我带来了不便,以确信这种态度太过于无耻.

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

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

  • Re:"我认为正确的解决方案是为Haskell的导入机制添加一个扩展来控制实例的导入"如果这个想法让任何人感兴趣,那么可能值得一看Scala语言的例子; 它具有非常类似的功能来控制'implicits'的范围,它可以像类型类实例一样使用. (15认同)
  • 我的软件是应用程序而不是库,因此给其他开发人员带来问题的可能性几乎为零.您可以将Publisher模块视为应用程序,将其余模块视为库,但如果我要分发库,那么它将没有Publisher,因此也就是孤立实例.但是如果我将实例移动到其他模块中,那么库将对HStringTemplate产生不必要的依赖.所以在这种情况下,我认为孤儿是可以的,但如果我在不同的背景下遇到同样的问题,我会听从你的建议. (5认同)
  • 特别是,这越来越成为图书馆发展的问题.Haskell上有超过2200个库,以及成千上万个单独模块中的数据库,拾取实例的风险大幅增加. (4认同)

yai*_*chu 41

来吧,压制这个警告!

你是一个很好的公司.Conal在"TypeCompose"中做到了."chp-mtl"和"chp-transformer"执行此操作,"control-monad-exception-mtl"和"control-monad-exception-monadsfd"执行此操作,等等.

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

{-# OPTIONS_GHC -fno-warn-orphans #-}
Run Code Online (Sandbox Code Playgroud)

编辑:

我承认伊兹在答案中提到的问题是真正的问题.然而,我认为不使用孤立的实例也是一个问题,我试图挑选"最少的所有邪恶",这是非常谨慎地使用孤儿实例.

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

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

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

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

现在回到幻想世界(或者希望是未来):我建议你在"真正需要"的时候尽量避免使用孤立实例

  • 是的,但可以说这些事件中的每一个都是某种秩序的错误.我想到了控制monad-exception-mtl和monads-fd中的错误实例.如果每个模块都被迫定义自己的类型或提供newtype包装,那么它就不那么突兀了.几乎每一个孤儿都是令人头疼的事情等待发生,如果没有其他事情需要你不断保持警惕,以确保它是酌情进口的. (5认同)
  • 谢谢.我想我会在这种特殊情况下使用它们,但多亏了Yitz,我现在可以更好地了解它们可能导致的问题. (2认同)

小智 35

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

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

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

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

  • 使用QuickCheck时,一个很好的例子是`(Ord k,Arbitrary k,Arbitrary v)⇒任意(Map kv)`. (2认同)

scl*_*clv 17

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

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


Try*_*ler 5

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

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