Perché non posso rendere String un'istanza di una typeclass?


85

Dato :

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

Voglio fare Stringun'istanza di Fooable:

instance Fooable String where
  toFoo = FooString

GHC quindi si lamenta:

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'

Se invece utilizzo [Char]:

instance Fooable [Char] where
  toFoo = FooString

GHC si lamenta:

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

Domanda :

  • Perché non posso creare String e istanza di una typeclass?
  • GHC sembra disposto a lasciarmi andare via con questo se aggiungo una bandiera extra. E 'questa una buona idea?

6
Questo è il tipo di domande che voto e contrassegno come preferite perché altrimenti so che in un prossimo futuro lo farei;)
Oscar Mederos

3
Per quanto riguarda il flag extra: probabilmente è una buona idea, a patto che ti fidi di GHC e capisci cosa fa il flag. Mi viene in mente Yesod : ti incoraggia a usare sempre il pragma OverloadedStrings quando scrivi app Yesod, e QuasiQuotes sono una necessità per le regole di instradamento di Yesod. Nota che invece di un flag in fase di compilazione, puoi anche mettere {-# LANGUAGE FlexibleInstances #-}(o qualsiasi altro pragma) all'inizio del tuo file .hs.
Dan Burton

Risposte:


65

Questo perché Stringè solo un alias di tipo per [Char], che è solo l'applicazione del costruttore di tipo []sul tipo Char, quindi questo sarebbe del formato ([] Char). che non ha la forma (T a1 .. an)perché Charnon è una variabile di tipo.

Il motivo di questa restrizione è impedire la sovrapposizione di istanze. Ad esempio, supponiamo che tu abbia avuto un instance Fooable [Char], e poi qualcuno è arrivato e ha definito un file instance Fooable [a]. Ora il compilatore non sarà in grado di capire quale vuoi usare e ti darà un errore.

Usando -XFlexibleInstances, stai sostanzialmente promettendo al compilatore che non definirai nessuna di queste istanze.

A seconda di ciò che stai cercando di ottenere, potrebbe essere meglio definire un wrapper:

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

4
Diciamo per amor di discussione che anch'io volevo davvero instance Fooable [a]. C'è un modo per fare in modo che la toFoofunzione si comporti in modo diverso se aè un Char?
John F. Miller

7
@ John: C'è un'estensione -XOverlappingInstancesche lo consentirà e scegli l'istanza più specifica. Consultare la guida per l'utente di GHC per i dettagli .
Hammar

18

Stai riscontrando due limitazioni delle classiche classi di caratteri Haskell98:

  • non consentono i sinonimi di tipo nelle istanze
  • non consentono tipi annidati che a loro volta non contengono variabili di tipo.

Queste restrizioni onerose vengono rimosse da due estensioni linguistiche:

  • -XTypeSynonymInstances

che ti consente di utilizzare sinimi di tipo (come Stringper [Char]) e:

  • -XFlexibleInstances

che sollevano le restrizioni sui tipi di istanza che sono della forma in T a b ..cui i parametri sono variabili di tipo. Il -XFlexibleInstancesflag consente all'head della dichiarazione di istanza di menzionare tipi annidati arbitrari.

Tieni presente che l'eliminazione di queste restrizioni a volte può portare a istanze sovrapposte , a quel punto potrebbe essere necessaria un'estensione linguistica aggiuntiva per risolvere l'ambiguità, consentendo a GHC di scegliere un'istanza per te.


Riferimenti ::


4

Le istanze flessibili non sono una buona risposta nella maggior parte dei casi. Alternative migliori sono avvolgere la stringa in un newtype o introdurre una classe helper in questo modo:

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

instance Element Char where
   listToFoo = FooString

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

Vedi anche: http://www.haskell.org/haskellwiki/List_instance


2

Aggiungendo a queste risposte, se non ti senti a tuo agio con l'eliminazione delle restrizioni, potrebbero esserci casi in cui potrebbe avere senso avvolgere la tua stringa in un nuovo tipo, che può essere un'istanza di una classe. Il compromesso sarebbe potenziale bruttezza, dover avvolgere e scartare il codice.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.