`DeriveAnyClass`和空实例之间有什么区别?

24

使用cassava包,以下代码可以编译:

{-# LANGUAGE DeriveGeneric #-}

import Data.Csv
import GHC.Generics

data Foo = Foo { foo :: Int } deriving (Generic)
instance ToNamedRecord Foo

但是以下内容不符合:

{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DeriveAnyClass #-}

import Data.Csv
import GHC.Generics

data Foo = Foo { foo :: Int } deriving (Generic, ToNamedRecord)
编译器报告:
test.hs:7:50:
    No instance for (ToNamedRecord Int)
      arising from the first field of ‘Foo’ (type ‘Int’)
    Possible fix:
      use a standalone 'deriving instance' declaration,
        so you can specify the instance context yourself
    When deriving the instance for (ToNamedRecord Foo)

这让我有两个问题:第二个版本为什么不与第一个相同?编译器为什么希望找到 ToNamedRecord Int 的一个实例?


2
我还没有看到DeriveAnyClass有任何有用的作用。但是,我确实看到它会产生编译时崩溃。我认为这个功能存在缺陷。 - dfeuer
1
这些天,这个问题中提出的代码确实可以编译!编译器的行为已经改变了——而且变得更好了。 - Daniel Wagner
的确,这个扩展在过去几年里得到了很多工作,我相信大部分是由Ryan Scott完成的。 - dfeuer
1个回答

15
NB:在评论中由David指出,自我写这篇文章以来,GHC已经更新。问题中的代码编译并且可以正常工作。所以请想象以下所有内容都是用过去时态描述的。

GHC文档中提到:

The instance context will be generated according to the same rules used when deriving Eq (if the kind of the type is *), or the rules for Functor (if the kind of the type is (* -> *)). For example

instance C a => C (a,b) where ...

data T a b = MkT a (a,b) deriving( C )

The deriving clause will generate

instance C a => C (T a b) where {}

The constraints C a and C (a,b) are generated from the data constructor arguments, but the latter simplifies to C a.

因此,根据Eq规则,您的派生子句将生成...
instance ToNamedRecord Int => ToNamedRecord Foo where

...这与...

请注意,这两者并不相同。
instance ToNamedRecord Foo where

...... 因为在您的情况下似乎没有instance ToNamedRecord Int,所以前者只有在范围内时才有效。

但我发现规范有些模糊。这个例子真的应该生成那段代码吗?还是应该生成instance (C a, C (a, b)) => instance C (T a b)并让求解器解决第二个约束?在您的示例中,似乎即使对于完全具体类型的字段也会生成这样的约束。

我不敢称之为错误,因为这就是Eq的工作方式,但鉴于DeriveAnyClass旨在使编写实例更快,它似乎不太直观。


4
谢谢,这很好地解释了事情!现在你强调了正在解决的问题(“实例应该给予什么上下文?”),我可以理解为什么 GHC 的人做出了他们的决定——尽管我开始认为这是一个错误的功能。它能正常工作的条件似乎非常特定,在这些条件下节省的工作似乎也相当少。 - Daniel Wagner
3
同意。使用“DeriveAnyClass”有用的大多数(全部?)类都会依赖于一个顶级超类,例如“Generic”或“Data”,而不是基于类型结构的递归上下文。对于“Eq”,这些规则是有意义的,因为生成的代码本身具有结构递归性。 - Benjamin Hodgson
1
这个答案现在已经过时了。正如丹尼尔·瓦格纳在评论中指出的那样,代码现在可以编译!这个答案至少应该更新以表明这一点,并链接到一个仍然包含“Eq”和“Functor”业务的旧版手册。最近的版本对于推断约束条件更加合理。 - dfeuer
@dfeuer 感谢您通知我,我已经在答案顶部添加了一条注释。 - Benjamin Hodgson

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接