DeriveAnyClass против пустого экземпляра

Предположим, что у меня есть это семейство типов, которое выдает ошибку настраиваемого типа во время компиляции, если переданный ему тип не является записью:

type family IsRecord (a :: Type) where
  ...

Теперь у меня есть этот класс типа, который имеет методы с реализациями по умолчанию, но требует, чтобы тип был записью, добавив ограничение IsRecord:

class IsRecord a => Foo a where
  foo :: Text
  foo = "foo"

При попытке использовать его неправильно, если мы используем его как обычный экземпляр с типом, который не является записью, он успешно не компилируется:

data Bar = Bar

instance Foo Bar   -- error: Bar is not a record

Но если я включу -XDeriveAnyClass и добавлю его к производному предложению, это не приведет к отказу от компиляции, полностью игнорируя ограничение:

data Bar = Bar
  deriving (Foo)

Я понимаю, что DeriveAnyClass генерирует пустое объявление экземпляра, что я и делаю в первом примере, но по-прежнему не вызывает ошибки. Что происходит?

Я использую GHC 8.6.4


person Nick Tchayka    schedule 14.06.2019    source источник
comment
Он унаследует ограничение, например instance IsRecord Bar => Foo Bar.   -  person Willem Van Onsem    schedule 14.06.2019
comment
Да, но это не должно компилироваться, верно?   -  person Nick Tchayka    schedule 14.06.2019
comment
почему нет? Ограничение ложное, но это не проблема. Если вы позже добавите экземпляр в модуль, который его использует, он станет доступным.   -  person Willem Van Onsem    schedule 14.06.2019
comment
Я понимаю, что на практике это не проблема, но мой вопрос касается в чем разница между этими двумя вещами.   -  person Nick Tchayka    schedule 14.06.2019
comment
что вы просто говорите, что если IsRecord Bar выполняется, тогда мы можем определить instance Foo Bar, как указано в этом экземпляре. На данный момент это ограничение не выполняется, если вы позже добавите экземпляр, например, в другой модуль, тогда ограничение будет истинным, и, следовательно, вы получите экземпляр. Таким образом, это добавляет дополнительную гибкость.   -  person Willem Van Onsem    schedule 14.06.2019
comment
Например, в исходном коде есть объявления экземпляров, такие как instance HasCallStack => PrintStack, которые, таким образом, доступны, если система использует стек вызовов. Таким образом, этот экземпляр доступен только тогда.   -  person Willem Van Onsem    schedule 14.06.2019
comment
Теперь понятно, спасибо! Если бы вы могли опубликовать ответ, я могу отметить его как решение. @WillemVanOnsem   -  person Nick Tchayka    schedule 14.06.2019


Ответы (1)


Вау! Я собирался отметить это как дубликат В чем разница между DeriveAnyClass и пустым экземпляром?, но похоже поведение GHC изменилось с тех пор, как этот вопрос был задан и дан ответ!

В любом случае, если вы спросите - либо с :i внутри ghci, либо с -ddump-deriv перед запуском ghci - что сделал компилятор, станет ясно, в чем разница в вашем случае:

> :i Bar
data Bar = Bar  -- Defined at test.hs:15:1
instance IsRecord Bar => Foo Bar -- Defined at test.hs:16:13

Действительно, если вы измените версию своего кода, отличную от DeriveAnyClass, чтобы она соответствовала, написав

instance IsRecord Bar => Foo Bar

вместо

instance Foo Bar

все работает нормально. Детали того, как был выбран контекст этого экземпляра, кажутся немного сложными; вы можете прочитать, что об этом говорится в руководстве GHC здесь, хотя я подозреваю, что описание здесь не совсем точное или неполное, поскольку я не получаю того же ответа, что и компилятор, если я строго следую правилам, изложенным в документация. (Я подозреваю, что истинный ответ заключается в том, что он сначала записывает экземпляр, а затем просто выполняет обычный вывод типов и копирует любые ограничения, которые он обнаруживает таким образом, в контекст экземпляра.)

person Daniel Wagner    schedule 14.06.2019