我正在设计一个可以从使用OverlappingInstances编译器标志中获益的库。但是everyone与smack进行了对此扩展的讨论并警告其危险性。我的问题是,是否存在在hackage上任何地方使用此扩展的示例?关于如何封装不良并正确使用扩展,是否有任何经验法则?
答案 0 :(得分:30)
也许一个思想实验会使这个扩展神秘化。
让我们假设我们已经放弃了使用多个模式案例定义的函数必须全部在一个地方的限制,这样您就可以在模块顶部编写foo ("bar", Nothing) = ...
,然后像{{ 1}}其他地方。事实上,让我们更进一步,并允许在完全不同的模块中定义案例!
如果您认为这听起来很混乱且容易出错,那么您就是对的。
为了恢复一些理智,我们可以添加一些限制。例如(ha,ha),我们可能需要以下属性来保存:
应该很清楚,匹配foo ("baz", Just x) = ...
或True
之类的简单构造函数很简单。我们也可以手动处理一些事情,并假设编译器可以消除文字歧义,例如上面的Nothing
和"bar"
。
另一方面,使用"baz"
这样的模式绑定参数变得笨拙 - 编写这样的模式意味着放弃了以后编写(x, Just y)
或(True, _)
等模式的能力,因为那样会造成歧义。更糟糕的是,模式守卫几乎无用,因为他们需要非常一般的比赛。许多常见的习语会产生无穷无尽的模糊不清,当然写一个“默认”的落后模式是完全不可能的。
这大致是类型类实例的情况。
我们可以通过放宽所需的属性来重获一些表现力:
请注意,我们现在处于这样一种情况:仅导入模块可以通过将新的更具体的模式引入范围来更改函数的行为。在涉及高阶函数的复杂情况下,事情可能会变得模糊。尽管如此,在许多情况下,问题不太可能发生 - 例如,在库中定义通用的直通模式,同时让客户端代码在需要时添加特定的案例。
这大致是(False, Just "foobar")
给你的地方。正如上面的例子所示,如果创建新的重叠总是不可能或不可取的,并且不同的模块最终不会看到不同的,冲突的实例,那么它可能就好了。
真正归结为OverlappingInstances
删除的限制是为了使用类型类在“开放世界”假设下明智地表现,以后可以添加任何可能的实例。通过放宽这些要求,你自己承担了这个负担;因此,请考虑可以添加新实例的所有方式以及这些方案中是否存在任何重大问题。如果你确信在晦涩和狡猾的角落情况下什么都不会破裂,那么继续使用扩展。
答案 1 :(得分:12)
大多数人请求重叠实例,因为他们需要基于约束的推理而不是类型导向的推理。类型定向推理的类型类和Haskell不能为约束定向推理提供优雅的解决方案。
但是,您仍然可以通过使用newtypes“封装善意”。鉴于以下实例定义容易出现重叠实例:
instance (SomeConstraint a) => SomeClass a where ...
您可以改为使用:
newtype N a = N { unN :: a }
instance (SomeConstraint a) => SomeClass (N a) where ...
现在,Haskell的类型系统具有适当的特定类型(即N a
),而不是在每种类型上无偿匹配。这使您可以控制实例的范围,因为N
newtype中包含的内容现在只会匹配。
答案 2 :(得分:2)
OverlappingInstances
允许你编写许多有用的东西,否则在类型类级别无法实现,绝大多数这些东西虽然可以重新组织以使用单个函数依赖
(这里写的是多种多样的风格)
class TypeEq (a :: k) (b :: k) (t :: Bool) | a b -> t where
typeEq :: Proxy a -> Proxy b -> HBool t
目前只能使用OverlappingInstance
实现(以完全通用的方式)。用例的例子包括Oleg将OOP编码到Haskell中。因此,我充分利用OverlappingInstances
的一个例子是this implementation of TypeEq
from the classic HList paper
这个特殊的功能可以通过编译器支持非常简单地提供(甚至可以在类型函数而不是fundep级别工作),因此在某个地方使用TypeEq的单个模块对我来说并不是那么糟糕。
当我从事危险的类型类hackery时,我经常发现IncoherentInstances
行为(选择第一个匹配的实例)更容易推理并且更灵活,因此至少在a的探索阶段使用它设计。一旦我拥有了我想要的东西,我就会试图摆脱扩展,特别注意表现不佳的人(比如这些)。