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

85

Дано :

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, похоже, позволит мне уйти от этого, если я добавлю дополнительный флаг. Это хорошая идея?
Джон Ф. Миллер
источник
6
За такие вопросы я голосую и отмечаю как избранные, потому что в противном случае я знаю, что в ближайшем будущем я бы их задавал;)
Оскар Медерос,
3
Что касается дополнительного флага: это, вероятно, хорошая идея, если вы доверяете GHC и понимаете, что делает флаг. На ум приходит Yesod : он побуждает всегда использовать прагму OverloadedStrings при написании приложений Yesod, а QuasiQuotes необходимы для правил маршрутизации Yesod. Обратите внимание, что вместо флага во время компиляции вы также можете поместить {-# LANGUAGE FlexibleInstances #-}(или любую другую директиву) в начало вашего файла .hs.
Дэн Бертон

Ответы:

65

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

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

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

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

newtype Wrapper = Wrapper String
instance Fooable Wrapper where
    ...
хаммар
источник
4
Допустим, для аргументации, что я действительно тоже этого хотел instance Fooable [a]. Есть ли способ заставить toFooфункцию вести себя по-другому, если aэто Char?
Джон Ф. Миллер,
7
@John: есть расширение, -XOverlappingInstancesкоторое позволяет это и выбирает наиболее конкретный экземпляр. См. Подробности в руководстве пользователя GHC .
хаммар
18

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

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

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

  • -XTypeSynonymInstances

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

  • -XFlexibleInstances

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

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


Ссылки ::

Дон Стюарт
источник
4

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

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

Лемминг
источник
2

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

Коуи
источник