Ответ 1
То, что вы пытаетесь сделать, сложно для GHC, потому что, как говорит GHC в сообщении об ошибке, типам семейств действительно не нужно быть инъективными.
Что означает инъективность?
Функция типа F
называется инъективной, если F x ~ F y
означает x ~ y
. Если F
является конструктором нормального типа, определенным через data
, тогда это всегда верно. Однако для семейств типов это не выполняется.
Например, нет никаких проблем при определении следующих экземпляров, учитывая ваше определение Object
:
instance Object Int where
type Unit Int = Int
unit = 0
instance Object Char where
type Unit Char = Int
unit = 1
Теперь, если вы пишете unit :: Int
, то как GHC может определить, следует ли оценивать его до 0
или 1
? Даже запись unit :: Unit Int
делает это более понятным, потому что
Unit Int ~ Int ~ Unit Char
поэтому все три типа должны быть взаимозаменяемыми. Поскольку Unit
не гарантируется быть инъективным, просто невозможно однозначно заключить из знания Unit x
знание x
...
Следствием является то, что Unit
можно определить, но не использовать.
Решение 1: аргументы Dummy или proxy
Вы указали наиболее распространенный способ исправления этой проблемы. Добавьте аргумент, который помогает GHC фактически определить аргумент типа, о котором идет речь, путем изменения сигнатуры типа на
unit :: obj -> Unit obj
или
unit :: Proxy obj -> Unit obj
для подходящего определения Proxy
, например просто
data Proxy a
Решение 2: Ручная проверка обратимости
Возможно, менее известный вариант заключается в том, что вы можете фактически доказать GHC, что ваша функция типа обратима.
Способ сделать это - определить семейство обратного типа
type family UnUnit obj :: *
и сделать обратимость суперклассовым ограничением класса типа:
class (UnUnit (Unit obj) ~ obj) => Object obj where
type Unit obj :: *
unit :: Unit obj
Теперь вам нужно сделать дополнительную работу. Для каждого экземпляра класса необходимо определить
фактический инверсный Unit
правильно. Например,
instance (Object obj, Object obj') => Object (obj, obj') where
type Unit (obj, obj') = (Unit obj, Unit obj')
unit = (unit, unit)
type instance UnUnit (obj, obj') = (UnUnit obj, UnUnit obj')
Но с учетом этой модификации определение typechecks. Теперь, если GHC встречает вызов Unit
в определенном типе T
и хочет определить тип S
, такой как Unit S ~ T
, он может применить ограничение суперкласса, чтобы сделать вывод, что
S ~ UnUnit (Unit S) ~ UnUnit T
Если бы мы попытались определить неверные экземпляры, как указано выше, для Object Int
и Object Char
, которые оба отображают Unit Int
и Unit Char
как Int
, это не сработает, потому что мы должны решить, должно ли UnObject Int
быть Int
или Char
, но не могло иметь обоих...