Warum kann ich String nicht zu einer Instanz einer Typklasse machen?

85

Gegeben :

data Foo =
  FooString String
  

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

Ich möchte Stringeine Instanz erstellen von Fooable:

instance Fooable String where
  toFoo = FooString

GHC beschwert sich dann:

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'

Wenn ich stattdessen benutze [Char]:

instance Fooable [Char] where
  toFoo = FooString

GHC beschwert sich:

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

Frage :

  • Warum kann ich keinen String und keine Instanz einer Typklasse erstellen?
  • GHC scheint bereit zu sein, mich damit davonkommen zu lassen, wenn ich eine zusätzliche Flagge hinzufüge. Ist das eine gute Idee?
John F. Miller
quelle
6
Dies ist die Art von Fragen, die ich unterstütze und als Favorit markiere, weil ich sonst weiß, dass ich sie in naher Zukunft stellen würde;)
Oscar Mederos
3
In Bezug auf die zusätzliche Flagge: Es ist wahrscheinlich eine gute Idee, solange Sie GHC vertrauen und verstehen, was die Flagge tut. Yesod kommt in den Sinn: Es empfiehlt sich, beim Schreiben von Yesod-Apps immer das Pragma OverloadedStrings zu verwenden, und QuasiQuotes sind eine Notwendigkeit für die Routing-Regeln von Yesod. Beachten Sie, dass Sie anstelle eines Flags zur Kompilierungszeit auch {-# LANGUAGE FlexibleInstances #-}(oder ein anderes Pragma) oben in Ihre .hs-Datei einfügen können.
Dan Burton

Antworten:

65

Dies liegt daran, dass Stringes sich nur um einen Typalias für handelt [Char], bei dem es sich lediglich um die Anwendung des Typkonstruktors []auf den Typ Charhandelt. Dies wäre also von der Form ([] Char). Das ist nicht von der Form, (T a1 .. an)weil Chares keine Typvariable ist.

Der Grund für diese Einschränkung besteht darin, überlappende Instanzen zu verhindern. Nehmen wir zum Beispiel an, Sie hatten eine instance Fooable [Char], und später kam jemand und definierte eine instance Fooable [a]. Jetzt kann der Compiler nicht mehr herausfinden, welches Sie verwenden möchten, und gibt Ihnen einen Fehler aus.

Durch die Verwendung -XFlexibleInstancesversprechen Sie dem Compiler grundsätzlich, keine solchen Instanzen zu definieren.

Je nachdem, was Sie erreichen möchten, ist es möglicherweise besser, einen Wrapper zu definieren:

newtype Wrapper = Wrapper String
instance Fooable Wrapper where
    ...
Hammar
quelle
4
Sagen wir aus Gründen der Argumentation, dass ich tatsächlich auch wollte instance Fooable [a]. Gibt es eine Möglichkeit, die toFooFunktion anders zu verhalten, wenn aes sich um ein Zeichen handelt?
John F. Miller
7
@ John: Es gibt eine Erweiterung, -XOverlappingInstancesdie dies ermöglicht und die spezifischste Instanz auswählt. Weitere Informationen finden Sie im GHC-Benutzerhandbuch .
Hammar
18

Bei klassischen Haskell98-Typklassen treten zwei Einschränkungen auf:

  • In Instanzen sind Typensynonyme nicht zulässig
  • Sie verbieten verschachtelte Typen, die wiederum keine Typvariablen enthalten.

Diese belastenden Einschränkungen werden durch zwei Spracherweiterungen aufgehoben:

  • -XTypeSynonymInstances

Hiermit können Sie Typ-Synoymen (wie Stringfür [Char]) verwenden und:

  • -XFlexibleInstances

Dadurch werden die Einschränkungen für Instanztypen aufgehoben, die die Form haben, T a b ..in der die Parameter Typvariablen sind. Mit dem -XFlexibleInstancesFlag kann der Kopf der Instanzdeklaration beliebige verschachtelte Typen erwähnen.

Beachten Sie, dass das Aufheben dieser Einschränkungen manchmal zu überlappenden Instanzen führen kann. Zu diesem Zeitpunkt ist möglicherweise eine zusätzliche Spracherweiterung erforderlich, um die Mehrdeutigkeit zu beheben, sodass GHC eine Instanz für Sie auswählen kann.


Referenzen: :

Don Stewart
quelle
4

FlexibleInstances sind in den meisten Fällen keine gute Antwort. Bessere Alternativen sind das Umschließen des Strings in einen neuen Typ oder das Einführen einer Hilfsklasse wie folgt:

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

instance Element Char where
   listToFoo = FooString

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

Siehe auch: http://www.haskell.org/haskellwiki/List_instance

Lemming
quelle
2

Wenn Sie diese Antworten ergänzen und die Einschränkungen nicht aufheben möchten, kann es Fälle geben, in denen es sinnvoll sein kann, Ihren String in einen neuen Typ zu verpacken, der eine Instanz einer Klasse sein kann. Der Kompromiss wäre potenzielle Hässlichkeit, wenn Sie Ihren Code ein- und auspacken müssen.

Kowey
quelle