Типы семейства Haskell, понимание сообщения об ошибке

При попытке использовать Data.Has Я писал код, подобный следующему:

data Name = Name; type instance TypeOf Name = Text
type NameRecord = FieldOf Name;

Я обнаружил:

instance I NameRecord where
  ...

Выдает ошибку компиляции, а именно:

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

В то время как:

instance (NameRecord ~ a) => I a where
  ...

Компилирует нормально.

Я считаю, что ошибка связана с этим билетом в GHC, помеченном как недействительный.

Ответ на билет говорит:

Я не уверен, что вы предлагаете. Мы не можем автоматически преобразовать

instance C (Fam Int) -- (1)

в

instance (Fam Int ~ famint) => C famint -- (2)

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

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

Может кто-нибудь уточнить это объяснение, возможно, с некоторым примером кода, где (1) не получается, но (2) нет, и почему?

1 ответ

Решение

Случай, когда (1) не выполняется, но (2) нет, тривиален; потому что синонимы типа (type ExampleOfATypeSynonym = ...) недопустимы в объявлениях экземпляров, но они разрешены в ограничениях, в любой ситуации, когда у вас есть только один экземпляр, подобный этому:

-- (1)
class Foo a
type Bla = ()
instance Foo Bla

... может быть преобразовано в:

-- (2)
class Foo a
type Bla = ()
instance (a ~ Bla) => Foo a

Единственная причина сбоя (1) заключается в том, что синонимы типов недопустимы в объявлениях экземпляров, и это потому, что синонимы типов похожи на функции типов: они обеспечивают одностороннее отображение имени типа на имя типа, поэтому, если у вас есть type B = A и instance Foo B, неочевидно, что экземпляр Foo A создается вместо. Правило существует так, что вы должны написать instance Foo A вместо этого, чтобы прояснить, что это тип, который на самом деле получает экземпляр.

Использование семейств типов не имеет значения в этом контексте, потому что проблема скорее в том, что вы используете синоним типа, NameRecord тип. Вы также должны иметь в виду, что если синоним типа удален и заменен на FieldOf Name напрямую компиляция все равно не удастся; это потому, что "семейство типов" - это просто расширенная версия синонимов типов, поэтому FieldOf Name также является "синонимом типа" для Name :> Text в данном контексте. Вместо этого вы должны использовать семейство данных и экземпляр данных, чтобы получить "двунаправленную" связь.

Более подробную информацию о семействах данных можно найти в документации GHC.


Я думаю, что вы имеете в виду "... где (2) терпит неудачу, но (1) не..."

Давайте представим, что у нас есть класс типов, например:

class Foo a where
  foo :: a

Теперь вы можете написать такие примеры:

 instance Foo Int where
   foo = 0

 instance Foo Float where
   foo = 0

 main :: IO ()
 main = print (foo :: Float)

Это работает, как и следовало ожидать. Однако, если вы преобразуете код в это:

{-# LANGUAGE FlexibleInstances, TypeFamilies #-}
class Foo a where
  foo :: a

instance (a ~ Int) => Foo a where
  foo = 0

instance (a ~ Float) => Foo a where
  foo = 0

main :: IO ()
main = print (foo :: Float)

Это не компилируется; это отображает ошибку:

test.hs:5:10:
    Duplicate instance declarations:
      instance a ~ Int => Foo a -- Defined at test.hs:5:10-27
      instance a ~ Float => Foo a -- Defined at test.hs:8:10-29

Итак, это пример, который вы надеялись найти. Теперь это происходит только в том случае, если существует более одного Foo который использует этот трюк. Это почему?

Когда GHC разрешает классы типов, он смотрит только на заголовок объявления экземпляра; то есть он игнорирует все до =>, Когда он выбрал экземпляр, он "фиксирует" его и проверяет ограничения перед => чтобы увидеть, правда ли они. Итак, сначала он видит два случая:

instance Foo a where ...
instance Foo a where ...

Совершенно невозможно решить, какой экземпляр использовать, основываясь только на этой информации.

Другие вопросы по тегам