Por que não posso tornar String uma instância de uma typeclass?


85

Dado :

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

Eu quero fazer Stringuma instância de Fooable:

instance Fooable String where
  toFoo = FooString

GHC então reclama:

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, em vez disso, eu usar [Char]:

instance Fooable [Char] where
  toFoo = FooString

GHC reclama:

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

Questão :

  • Por que não posso fazer String e instância de uma typeclass?
  • GHC parece disposto a me deixar escapar com isso se eu adicionar um sinalizador extra. isso é uma boa ideia?

6
Este é o tipo de pergunta que eu voto positivamente e marquei como favorita, porque caso contrário, eu sei que em um futuro próximo eu estaria fazendo isso;)
Oscar Mederos

3
Em relação ao sinalizador extra: provavelmente é uma boa ideia, desde que você confie no GHC e entenda o que o sinalizador faz. Yesod vem à mente: incentiva você a sempre usar o pragma OverloadedStrings ao escrever aplicativos Yesod, e QuasiQuotes são uma necessidade para as regras de roteamento de Yesod. Observe que, em vez de um sinalizador em tempo de compilação, você também pode colocar {-# LANGUAGE FlexibleInstances #-}(ou qualquer outro pragma) no topo do seu arquivo .hs.
Dan Burton

Respostas:


65

Isso ocorre porque Stringé apenas um alias de tipo para [Char], que é apenas a aplicação do construtor de tipo []no tipo Char, então isso seria do formulário ([] Char). que não tem a forma (T a1 .. an)porque Charnão é uma variável de tipo.

O motivo dessa restrição é evitar a sobreposição de instâncias. Por exemplo, digamos que você tivesse um instance Fooable [Char]e, mais tarde, alguém apareceu e definiu um instance Fooable [a]. Agora, o compilador não será capaz de descobrir qual você deseja usar e apresentará um erro.

Ao usar -XFlexibleInstances, você basicamente promete ao compilador que não definirá nenhuma dessas instâncias.

Dependendo do que você está tentando realizar, pode ser melhor definir um wrapper:

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

4
Vamos dizer, para fins de argumentação, que eu também queria instance Fooable [a]. Existe uma maneira de fazer a toFoofunção se comportar de maneira diferente se afor um Char?
John F. Miller

7
@John: Existe uma extensão -XOverlappingInstancesque permite isso e escolhe a instância mais específica. Consulte o guia do usuário do GHC para obter detalhes .
hammar

18

Você está enfrentando duas limitações de typeclasses Haskell98 clássicas:

  • eles não permitem sinônimos de tipo em instâncias
  • eles não permitem tipos aninhados que, por sua vez, não contêm variáveis ​​de tipo.

Essas restrições onerosas são eliminadas por duas extensões de linguagem:

  • -XTypeSynonymInstances

que permite usar sinônimos de tipo (como Stringpara [Char]) e:

  • -XFlexibleInstances

que levantam as restrições sobre os tipos de instância sendo da forma em T a b ..que os parâmetros são variáveis ​​de tipo. O -XFlexibleInstancessinalizador permite que o cabeçalho da declaração da instância mencione tipos aninhados arbitrários.

Observe que o levantamento dessas restrições às vezes pode levar à sobreposição de instâncias ; nesse ponto, uma extensão de linguagem adicional pode ser necessária para resolver a ambigüidade, permitindo que o GHC escolha uma instância para você.


Referências ::


4

FlexibleInstances não é uma boa resposta na maioria dos casos. Melhores alternativas são envolver a String em um novo tipo ou introduzir uma classe auxiliar como esta:

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

instance Element Char where
   listToFoo = FooString

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

Veja também: http://www.haskell.org/haskellwiki/List_instance


2

Somando-se a essas respostas, se você não se sentir confortável em suspender as restrições, pode haver casos em que faria sentido envolver sua String em um novo tipo, que pode ser uma instância de uma classe. A compensação seria a feiura potencial, ter que embrulhar e desembrulhar em seu código.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.