Почему я не могу сделать 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:
- они запрещают синонимы типов в случаях
- они запрещают вложенные типы, которые, в свою очередь, не содержат переменных типа.
Эти обременительные ограничения снимаются двумя языковыми расширениями:
который позволяет использовать синоимы типа (например, String
для [Char]
) и:
которые снимают ограничения на типы экземпляров, имеющие вид 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, который может быть экземпляром класса. Компромисс был бы потенциальным уродством, которому пришлось бы обернуть и развернуть в вашем коде.