2016-09-07 2 views
22

Используя cassava пакет, следующий компилирует:В чем разница между `DeriveAnyClass` и пустым экземпляром?

{-# 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?

+1

Я еще не видел 'DeriveAnyClass' делать что-нибудь полезное. Однако я видел, что это приводит к сбоям во время компиляции. Понятно, что это багги. – dfeuer

ответ

15

The GHC docs говорят:

Контекст экземпляр будет создан в соответствии с теми же правилами , используемых при выводе Eq (если вид типа является *), или правила для Functor (если вид от (* -> *)). Например

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

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

Предложение deriving будет генерировать

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

Ограничения, C a и C (a,b) генерируются из аргументов конструктора данных, но последних упрощается до C a.

Таким образом, в соответствии с правилами Eq, ваша статья deriving генерирует ...

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 приняли решение, которое они сделали, - и я тем не менее начинаю считать это недостоверным , Условия, при которых он будет «просто работать», кажутся невероятно конкретными, и работа, которую он сохраняет в этих условиях, кажется довольно маленькой. –

+3

Согласен. Большинство (все?) классов, для которых «DeriveAnyClass» полезен, будет полагаться на суперкласс высшего уровня, такой как «Generic» или «Data», а не на рекурсивный контекст, основанный на структуре типа. Правила имеют смысл для 'Eq', потому что сгенерированный код сам по себе является структурно рекурсивным. –

Смежные вопросы