Как запретить Haskell импортировать один и тот же экземпляр из двух модулей?

Я использую следующий класс типов:

module T where
class T a where
  v :: a

Реализованный мной экземпляр T Int:

import T
import A (av)

instance T Int where
  v = 0

main = putStrLn (av ++ show v)

И модуль, значение из которого я хочу использовать, у которого также есть экземпляр T Int.

module A where
import T
instance T Int where
  v = 0
av = "value from A"

Проблема в том, что это не работает:

$ runghc Main.hs 

Main.hs:4:9:
    Duplicate instance declarations:
      instance T Int -- Defined at Main.hs:4:9-13
      instance T Int -- Defined at A.hs:3:9-13

Haskell жалуется, что для одного и того же экземпляра есть 2 объявления. Как я могу сказать ему не импортировать экземпляр из B, или объединить оба экземпляра, или использовать только экземпляр из Main?


person Dog    schedule 08.05.2013    source источник
comment
Не делай этого. Правильное решение - объявить экземпляр только в одном месте.   -  person hammar    schedule 08.05.2013
comment
@hammar: я не писал T или B, но я хочу использовать значение из B, и я хочу создать экземпляр T.   -  person Dog    schedule 08.05.2013
comment
Ах, это еще больше усложняет задачу. В этом случае вы можете сделать newtype оболочку существующего типа и вместо этого написать свой экземпляр для newtype.   -  person hammar    schedule 08.05.2013


Ответы (1)


К сожалению, вы не можете контролировать, как экземпляры импортируются и экспортируются; см. Имеет ли импорт Haskell побочные эффекты?.

Это означает, что вам придется реорганизовать свой код, чтобы гарантировать, что экземпляр определен только в одном файле. В общем, лучше всего определять только экземпляр в файле, который определяет либо класс, либо тип данных - фактически, есть даже предупреждение о «сиротских» экземплярах, которые не следуют этому правилу. (Посмотрите Сиротские экземпляры в Haskell, где подробно обсуждается, почему вам следует избегать бесхозных экземпляров.)

Однако, если по какой-то причине это невозможно, вы все равно можете произвольно выбрать один из файлов, чтобы сохранить его, или даже создать новый модуль, который будет импортирован всеми файлами, которым необходим этот конкретный экземпляр.

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

instance T Int where v = 0
{- And in a different file: -}
instance T Int where v = 1

На самом деле нет очевидного способа устранить двусмысленность этих двух без значительного изменения того, как работает система классов типов Haskell.

Поскольку вы сами написали один из экземпляров, просто удалите его. Поскольку он такой же, как и предопределенный, просто импортируйте этот модуль везде, где вам нужно его использовать.

person Tikhon Jelvis    schedule 08.05.2013
comment
Это означает, что вам придется реорганизовать свой код, чтобы гарантировать, что экземпляр определен только в одном файле. Как я могу это сделать? Я не писал T или B. - person Dog; 08.05.2013
comment
Ой. Тогда да, это плохо. Вот почему вы не должны писать бесхозные экземпляры. Я бы сообщил об ошибке в модуле, у которого есть сиротский экземпляр, то есть в том, который имеет экземпляр без определения ни класса, ни типа. - person Tikhon Jelvis; 08.05.2013
comment
@Dog: На самом деле, может быть, я неправильно понял вашу проблему. Поскольку вы написали один из экземпляров, просто удалите его. Если нужно, напишите это для newtype. - person Tikhon Jelvis; 08.05.2013