`DeriveAnyClass`和一个空实例有什么区别?

Dan*_*ner 24 generics haskell deriving

使用cassava包,以下编译:

{-# LANGUAGE DeriveGeneric #-}

import Data.Csv
import GHC.Generics

data Foo = Foo { foo :: Int } deriving (Generic)
instance ToNamedRecord Foo
Run Code Online (Sandbox Code Playgroud)

但是,以下内容不是:

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

import Data.Csv
import GHC.Generics

data Foo = Foo { foo :: Int } deriving (Generic, ToNamedRecord)
Run Code Online (Sandbox Code Playgroud)

编译器报告:

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)
Run Code Online (Sandbox Code Playgroud)

这给我留下两个问题:为什么第二个版本与第一个版本不一样?为什么编译器希望找到一个实例ToNamedRecord Int呢?

Ben*_*son 15

GHC文件说:

实例上下文将根据导出时使用的相同规则生成Eq(如果类型的类型是*),或者Functor的规则(如果类型的类型是(* -> *)).例如

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

data T a b = MkT a (a,b) deriving( C )
Run Code Online (Sandbox Code Playgroud)

deriving子句将生成

instance C a => C (T a b) where {}
Run Code Online (Sandbox Code Playgroud)

的约束C aC (a,b)从数据构造器参数生成的,但后者简化为C a.

所以,根据Eq规则,你的deriving条款生成......

instance ToNamedRecord Int => ToNamedRecord Foo where
Run Code Online (Sandbox Code Playgroud)

......这跟......不一样

instance ToNamedRecord Foo where
Run Code Online (Sandbox Code Playgroud)

......因为前者只有instance ToNamedRecord Int在范围内才有效(在你的情况下似乎没有).

但我觉得这个规范有些含糊不清.该示例是否真的会生成该代码,还是应该生成instance (C a, C (a, b)) => instance C (T a b)并让解算器释放第二个约束?在您的示例中,即使对于具有完全具体类型的字段,它也会生成此类约束.

我毫不犹豫地称这是一个错误,因为它是如何Eq工作的,但鉴于这DeriveAnyClass是为了使编写实例更快,它似乎不直观.

  • 谢谢,这完全解释了事情!现在您已经突出显示了正在解决的问题("应该给实例提供什么上下文?"),我可以看到为什么GHC人员做出了他们做出的决定 - 然而我仍然开始认为这是一个错误."Just Work"的条件看起来非常具体,而且在这些条件下保存的工作看起来确实很小. (4认同)
  • 我同意."DeriveAnyClass"有用的大多数(全部?)类将依赖于顶级超类,如`Generic`或`Data`,而不是基于类型结构的递归上下文.这些规则对于'Eq`是有意义的,因为生成的代码本身在结构上是递归的. (3认同)