Я хочу определить модуль Haskell, который экспортирует класс C
и экземпляры C
для различных типов. Поскольку я хочу определить множество экземпляров, которые могут быть определены автоматически по заданной схеме, я определил вспомогательную функцию TH для определения экземпляра примерно так:
module Foo (C) where
class C a
defineCInstance :: TypeQ -> DecsQ
defineCInstance t =
[d|
instance C $t
|]
defineCInstance [t| () |]
defineCInstance [t| Char |]
defineCInstance [t| Int |]
Однако приведенный выше пример не ' t скомпилировать, потому что из-за ограничений на стадии нельзя использовать defineCInstance
в соединении в том же модуле, в котором он определен. Это можно исправить, переместив defineCInstance
в новый модуль (назовем его Foo.Internal
) и импортировать его в Foo
. Однако, когда я просто перемещаю defineCInstance
в другой модуль, у него не будет C
в области видимости, и в настоящее время я вижу два способа обойти это.
Решение 1, Переместите C
в Foo.Internal
как ну
Если я переместлю и defineCInstance
, и C
в Foo.Internal
defineCInstance
, можно легко обратиться к C
, и я смогу без проблем склеить defineCInstance
в Foo
. Однако экземпляр, определенный в Foo
, больше не будет определяться в том же модуле, что и C
, и, таким образом, станет потерянным экземпляром. Я хотел бы избежать этого, потому что тогда мне пришлось бы замолчать предупреждение для сирот, которое является AFAIK возможным только для всего файла и могло бы заставить замолчать предупреждение для других непреднамеренных экземпляров сирот в том же файле. Я также заметил, что пикша перечисляет все экземпляры как бесхозные экземпляры в документации, чего я также хотел бы избежать.
Решение 2. Не позволяйте defineCInstance
напрямую ссылаться на C
.
Вместо того, чтобы ссылаться на instance C
внутри defineCInstance
, я мог бы использовать что-то вроде instance $(conT $ mkName "C")
. Однако это скрывает зависимость между C
и defineCInstance
от компилятора, что повышает вероятность ошибок. Например, если я переименую C
, но забуду изменить имя в defineCInstance
, компилятор все равно с радостью скомпилирует defineCInstance
. Хуже того, он полагается на правильное значение C
, находящееся в области на месте соединения defineCInstance
. Если у пользователя неправильная область действия C
, сгенерированный код будет совершенно бессмысленным.
Есть ли способ определить C
, defineCInstance
и экземпляры для C
таким образом, который по-прежнему позволяет ссылаться на C
непосредственно из defineCInstance
и избегает определения экземпляров-сирот?