Pourquoi ne puis-je pas faire de String une instance d'une classe de types?

85

Donné :

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

Je veux créer Stringune instance de Fooable:

instance Fooable String where
  toFoo = FooString

GHC se plaint alors:

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'

Si à la place j'utilise [Char]:

instance Fooable [Char] where
  toFoo = FooString

GHC se plaint:

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]'

Question :

  • Pourquoi ne puis-je pas créer une chaîne et une instance d'une classe de types?
  • GHC semble prêt à me laisser m'en tirer si j'ajoute un drapeau supplémentaire. Est-ce une bonne idée?
John F. Miller
la source
6
C'est le genre de questions que je vote pour et que je marque comme favori car sinon je sais que dans un proche avenir, je le poserais;)
Oscar Mederos
3
En ce qui concerne le drapeau supplémentaire: c'est probablement une bonne idée, tant que vous faites confiance à GHC et que vous comprenez ce que fait le drapeau. Yesod vient à l'esprit: il vous encourage à toujours utiliser le pragma OverloadedStrings lors de l'écriture d'applications Yesod, et les QuasiQuotes sont une nécessité pour les règles de routage Yesod. Notez qu'au lieu d'un drapeau au moment de la compilation, vous pouvez également mettre {-# LANGUAGE FlexibleInstances #-}(ou tout autre pragma) en haut de votre fichier .hs.
Dan Burton

Réponses:

65

Cela est dû au Stringfait qu'il s'agit simplement d'un alias de type pour [Char], qui est juste l'application du constructeur de type []sur le type Char, donc ce serait de la forme ([] Char). qui n'est pas de la forme (T a1 .. an)car Charn'est pas une variable de type.

La raison de cette restriction est d'éviter le chevauchement des instances. Par exemple, disons que vous avez eu un instance Fooable [Char], puis quelqu'un est venu plus tard et a défini un instance Fooable [a]. Maintenant, le compilateur ne sera pas en mesure de déterminer celui que vous vouliez utiliser et vous donnera une erreur.

En utilisant -XFlexibleInstances, vous promettez au compilateur de ne pas définir de telles instances.

En fonction de ce que vous essayez d'accomplir, il peut être préférable de définir un wrapper:

newtype Wrapper = Wrapper String
instance Fooable Wrapper where
    ...
Hammar
la source
4
Disons, par souci d'argumentation, que je voulais instance Fooable [a]aussi. Existe-t-il un moyen de faire en sorte que la toFoofonction se comporte différemment s'il as'agit d'un Char?
John F. Miller
7
@John: Il existe une extension -XOverlappingInstancesqui permet cela, et choisissez l'instance la plus spécifique. Consultez le guide de l'utilisateur du GHC pour plus de détails .
hammar
18

Vous rencontrez deux limitations des classes de types classiques Haskell98:

  • ils interdisent les synonymes de type dans les instances
  • ils interdisent les types imbriqués qui ne contiennent pas à leur tour de variables de type.

Ces lourdes restrictions sont levées par deux extensions de langue:

  • -XTypeSynonymInstances

qui vous permet d'utiliser des synoyms de type (comme Stringpour [Char]), et:

  • -XFlexibleInstances

qui lèvent les restrictions sur les types d'instances étant de la forme T a b ..où les paramètres sont des variables de type. L' -XFlexibleInstancesindicateur permet à la tête de la déclaration d'instance de mentionner des types imbriqués arbitraires.

Notez que la levée de ces restrictions peut parfois entraîner des instances qui se chevauchent , auquel cas une extension de langue supplémentaire peut être nécessaire pour résoudre l'ambiguïté, permettant à GHC de choisir une instance pour vous.


Références ::

Don Stewart
la source
4

Les instances flexibles ne sont pas une bonne réponse dans la plupart des cas. De meilleures alternatives enveloppent la chaîne dans un nouveau type ou introduisent une classe d'assistance comme ceci:

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

instance Element Char where
   listToFoo = FooString

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

Voir aussi: http://www.haskell.org/haskellwiki/List_instance

Lemming
la source
2

En ajoutant à ces réponses, si vous n'êtes pas à l'aise avec la levée des restrictions, il peut y avoir des cas où il pourrait être judicieux d'envelopper votre String dans un nouveau type, qui peut être une instance d'une classe. Le compromis serait la laideur potentielle, avoir à envelopper et à déballer votre code.

Kowey
la source