带有嵌套参数的类型的实例

时间:2019-06-09 00:53:07

标签: haskell

在此示例中,我可以得出Eq:

data A f t = A (f t) deriving (Eq)

但是在此示例中:

data B f t = B (f (f t)) deriving (Eq)

我收到此错误:

> No instance for (Eq (f (f t)))
>   arising from the first field of ‘B’ (type ‘f (f t)’)
> Possible fix:
>   use a standalone 'deriving instance' declaration,
>     so you can specify the instance context yourself

这有效:

{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE StandaloneDeriving #-}
...
data B f t = B (f (f t))
deriving instance (Eq (f (f t)), Eq (f t)) => Eq (B f t)

但是我已经读过使用UndecidableInstances可能是个坏主意,而且我不确定什么时候可以,什么时候不能。

我尝试了这个,并且有效:

data B f t = B (f (f t))

instance (Eq1 f, Eq t) => (Eq (B f t)) where
  (B x) == (B y) = eq1 x y

但是我也想将B设为NFDataReadShow的实例,而我不想写Read,{ {1}}和Show实例以及NFDataEq1Show1实例以及NFData1的类。

我有3个问题:

  1. 为什么第一个示例可以编译而第二个示例不能编译?
  2. 使用UndecidableInstances难道是时候吗?
  3. 是否可以在不编写所有这些实例的情况下做我想做的事情?

1 个答案:

答案 0 :(得分:2)

这个确切的示例出现在GHC手册的Inferred context for deriving clauses部分中。如此处所述,GHC在推断用于派生实例的上下文时采取保守的立场,要求推断上下文中的每个约束必须仅由类型变量组成,且不重复。由于(f (f t))具有重复的类型变量f,因此将其拒绝。请注意,以下内容将被接受:

data C f g t = C (f (g t)) deriving (Eq)

手册中给出的解决方案是使用独立的派生子句。正如评论中指出的,以下内容就足够了:

deriving instance Eq (f (f t)) => Eq (B f t)

但是,由于类型变量f在约束中的出现频率比在“实例头” Eq (B f t)中的出现频率更高,因此它违反了Instance termination rules中记录的规则(特别是第一个Paterson条件),以确保类型检查器不会循环。因为这些规则足够但不是必需的,因此即使它们违反了规则,仍有许多实例声明可以正常工作。启用UndecidableInstances可以启用它们。

没有特别的理由不打开UndecidableInstances。在最坏的情况下,您将创建一个导致类型检查器循环的实例,并且编译将失败,并显示错误消息,建议您使用-freduction-depth={n}增加堆栈深度(如果您拥有真正的版本,这将无济于事。环)。请注意,这只是一个编译时问题。如果代码使用UndecidableInstances进行编译,则不必担心将来会出现运行时危险。

请注意,我不需要打开FlexibleContexts。以下工作正常:

{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE UndecidableInstances #-}

data B f t = B (f (f t))
deriving instance Eq (f (f t)) => Eq (B f t)
deriving instance Read (f (f t)) => Read (B f t)
deriving instance Show (f (f t)) => Show (B f t)

对于手动派生的实例,以下似乎可行,并且不需要NFData1或类似的内容:

instance NFData (f (f t)) => NFData (B f t) where
  rnf (B fft) = rnf fft