排除最近的一些问题,我认为我将把注意力转向旧的忌王OverlappingInstances
。
几年前,我可能一直在认真地问过这个问题:毕竟,您可以提供有用的默认实例,而其他人可以在需要时使用更具体的实例来覆盖它们,这有什么不好的呢?
在此过程中,我对OverlappingInstances
确实不是那么干净,最好避免使用这种观点表示赞赏。主要是因为与其他大型扩展程序不同,它在理论上没有很好的基础。
但是考虑一下,如果被问到,我不确定是否可以向他人解释这件事到底有什么不好。
我正在寻找的是使用OverlappingInstances
可能导致不良情况发生的方法的特定示例,无论是通过颠覆类型系统或其他不变式,还是仅仅出于意外或混乱。
我知道的一个特殊问题是,它破坏了仅添加或删除单个模块导入不能更改程序含义的属性,因为在扩展名启用的情况下,可以静默添加或删除新的实例重叠。虽然我可以看到为什么这令人不快,但我看不到为什么它令人震惊。
额外的问题:只要我们讨论的是有用的但理论上没有充分根据的扩展,可能导致不良后果,那么GeneralizedNewtypeDeriving
为何不会得到同样的不良说唱?是因为否定可能性更容易定位吗?更容易看到会导致问题的原因并说“不要那样做”?
(注意:如果答案的首字母放在OverlappingInstances
上,而不是需要较少说明的IncoherentInstances
上,我更愿意。)
编辑:类似问题here也有很好的答案。
最佳答案
haskell语言试图遵守的一个原则是在给定的模块中添加额外的方法/类或实例,不应导致依赖于该给定模块的任何其他模块无法编译或具有不同的行为(只要从属模块)使用显式导入列表)。
不幸的是,这被OverlappingInstances打破了。例如:
模块A:
{-# LANGUAGE FlexibleInstances, OverlappingInstances, MultiParamTypeClasses, FunctionalDependencies #-}
module A (Test(..)) where
class Test a b c | a b -> c where
test :: a -> b -> c
instance Test String a String where
test str _ = str
模块B:
module B where
import A (Test(test))
someFunc :: String -> Int -> String
someFunc = test
shouldEqualHello = someFunc "hello" 4
shouldEqualHello
在模块B中等于“hello”。现在在A中添加以下实例声明:
instance Test String Int String where
test s i = concat $ replicate i s
如果这不影响模块B,则将是更可取的。它在此添加之前起作用,而后应起作用。不幸的是,事实并非如此。
模块B仍在编译,但是现在
shouldEqualHello
现在等于"hellohellohellohello"
。即使最初使用的方法没有更改,该行为也已更改。更糟糕的是,由于无法选择不从模块导入实例,因此无法恢复到旧的行为。可以想象,这对向后兼容非常不利,因为您不能安全地向使用重叠实例的类中添加新实例,因为这可能会更改使用该模块的代码的行为(如果正在编写库代码,则尤其如此)。这比编译错误更糟糕,因为可能很难跟踪更改。
我认为使用重叠实例的唯一安全时间是在编写您知道永远不需要其他实例的类时。如果您正在执行一些棘手的基于类型的代码,则可能会发生这种情况。