Почему я не могу сделать String экземпляром класса?

Учитывая

data Foo =
  FooString String
  …

class Fooable a where --(is this a good way to name this?)
  toFoo :: a -> Foo

Я хочу сделать String экземпляр Fooable:

instance Fooable String where
  toFoo = FooString

GHC затем жалуется:

Illegal instance declaration for `Fooable String'
    (All instance types must be of the form (T t1 ... tn)
     where T is not a synonym.
     Use -XTypeSynonymInstances if you want to disable this.)
In the instance declaration for `Fooable String'

Если вместо этого я использую [Char]:

instance Fooable [Char] where
  toFoo = FooString

GHC жалуется:

Illegal instance declaration for `Fooable [Char]'
   (All instance types must be of the form (T a1 ... an)
    where a1 ... an are type *variables*,
    and each type variable appears at most once in the instance head.
    Use -XFlexibleInstances if you want to disable this.)
In the instance declaration for `Fooable [Char]'

Вопрос

  • Почему я не могу создать String и экземпляр класса?
  • GHC, похоже, желает, чтобы я сошел с рук, если добавлю дополнительный флаг. Это хорошая идея?

Ответ 1

Это потому, что String является просто псевдонимом типа для [Char], который является просто приложением конструктора типа [] для типа Char, поэтому это будет иметь вид ([] Char). который не имеет формы (T a1 .. an), потому что Char не является переменной типа.

Причиной этого ограничения является предотвращение дублирования экземпляров. Например, предположим, что у вас есть instance Fooable [Char], а затем кто-то позже подошел и определил instance Fooable [a]. Теперь компилятор не сможет определить, какой из них вы хотите использовать, и вы получите сообщение об ошибке.

Используя -XFlexibleInstances, вы в основном обещаете компилятору, что вы не будете определять такие экземпляры.

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

newtype Wrapper = Wrapper String
instance Fooable Wrapper where
    ...

Ответ 2

Вы используете два ограничения классических классов Haskell98:

  • они запрещают синонимы типов в случаях
  • они запрещают вложенные типы, которые, в свою очередь, не содержат переменных типа.

Эти обременительные ограничения снимаются двумя языковыми расширениями:

  • -XTypeSynonymInstances

который позволяет использовать синоимы типа (например, String для [Char]) и:

  • -XFlexibleInstances

которые снимают ограничения на типы экземпляров, имеющие вид T a b .., где параметры являются переменными типа. Флаг -XFlexibleInstances позволяет заголовку объявления экземпляра указать произвольные вложенные типы.

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


Литература::

Ответ 3

FlexibleInstances не являются хорошим ответом в большинстве случаев. Лучшие альтернативы обертывают String в newtype или вводят вспомогательный класс следующим образом:

class Element a where
   listToFoo :: [a] -> Foo

instance Element Char where
   listToFoo = FooString

instance Element a => Fooable [a] where
   toFoo = listToFoo

Смотрите также: http://www.haskell.org/haskellwiki/List_instance

Ответ 4

Добавляя к этим ответам, если вам неудобно снимать ограничения, могут быть случаи, когда имеет смысл обернуть вашу String в newtype, который может быть экземпляром класса. Компромисс был бы потенциальным уродством, которому пришлось бы обернуть и развернуть в вашем коде.