Предотвращение появления бесхозных экземпляров при определении экземпляров с помощью шаблона haskell - PullRequest
2 голосов
/ 11 марта 2020

Я хочу определить модуль 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 и избегает определения экземпляров-сирот?

1 Ответ

2 голосов
/ 11 марта 2020
  1. Определите внутренний defineCInstance_, который принимает Name из C в качестве параметра;

  2. Во внешнем Foo модуле, содержащем класс, используйте defineCInstance_ ''Foo.C для стандартных экземпляров;

  3. Экспорт defineCInstance = defineCInstance_ ''Foo.C, не позволяющий пользователям использовать неправильное имя.

В шаге 2 вы можете поместить все соответствующие типы в список и обойти его все сразу, так что вам нужно только дважды ссылаться на имя ''Foo.C: один раз в соединении, один раз в экспортированном defineCInstance.

...