作为一个(有点牵强附会的)例子,假设我想使用泛型来确定一个类型是否是无人居住的(没有非底部值)。在大多数情况下,我可以很好地解决这个问题:
问题在于我需要定义一个
class Absurd a where
absurd :: a -> b
default absurd :: (Generic a, GAbsurd (Rep a)) => a -> b
absurd = gabsurd . from
class GAbsurd f where
gabsurd :: f p -> b
instance GAbsurd f => GAbsurd (M1 i c f) where
gabsurd (M1 x) = gabsurd x
instance Absurd c => GAbsurd (K1 i c) where
gabsurd (K1 x) = absurd x
instance GAbsurd V1 where
gabsurd = \case
instance (GAbsurd a, GAbsurd b) => GAbsurd (a :+: b) where
gabsurd (L1 x) = gabsurd x
gabsurd (R1 x) = gabsurd x
问题在于我需要定义一个
:*:
的实例。只需要一个GAbsurd a
或GAbsurd b
中的一个来实现GAbsurd (a :*: b)
,但是我能做到的最好的情况是要求两个都有,这太过严格了。例如:data Void
data X = X Char Void
显然,X
是一个“荒谬”类型,但不能推断出来,因为虽然Void
是荒谬的,但Char
不是,所以约束条件(Absurd Char, Absurd Void)
无法解决。
我想做的是使用某种“约束析取”,如下所示:
instance (GAbsurd a || GAbsurd b) => GAbsurd (a :*: b) where
gabsurd (x :*: y) = case eitherConstraint of
LeftC -> gabsurd x
RightC -> gabsurd y
然而,在开放世界假设下,这是不可能的。实际上,Data.Boring
中的代码(也包括Absurd
)完全忽略了这个问题:
GAbsurd (f :*: g)
有两个合理的实例,因此我们两个都没有定义。
这就引出了一个问题:为什么开放世界假设如此重要? 我最好的猜测是它与孤儿实例有关,但即使是这样, GHC也应该有所有导入模块的列表吧?