¿Por qué no puedo hacer de String una instancia de una clase de tipos?

85

Dado :

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

Quiero hacer Stringuna instancia de Fooable:

instance Fooable String where
  toFoo = FooString

GHC luego se queja:

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 en cambio uso [Char]:

instance Fooable [Char] where
  toFoo = FooString

GHC se queja:

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

Pregunta :

  • ¿Por qué no puedo hacer una cadena y una instancia de una clase de tipo?
  • GHC parece dispuesto a dejarme salirse con la suya si agrego una bandera adicional. ¿Es esta una buena idea?
John F. Miller
fuente
6
Este es el tipo de preguntas que voto a favor y marco como favoritas porque, de lo contrario, sé que en un futuro cercano las estaría haciendo;)
Oscar Mederos
3
Con respecto a la bandera adicional: probablemente sea una buena idea, siempre que confíe en GHC y comprenda lo que hace la bandera. Me viene a la mente Yesod : te anima a usar siempre el pragma OverloadedStrings al escribir aplicaciones de Yesod, y QuasiQuotes son una necesidad para las reglas de enrutamiento de Yesod. Tenga en cuenta que en lugar de una bandera en tiempo de compilación, también puede poner {-# LANGUAGE FlexibleInstances #-}(o cualquier otro pragma) en la parte superior de su archivo .hs.
Dan Burton

Respuestas:

65

Esto se debe a que Stringes solo un alias de tipo para [Char], que es solo la aplicación del constructor de tipo []en el tipo Char, por lo que sería de la forma([] Char) . que no es de la forma (T a1 .. an)porque Charno es una variable de tipo.

El motivo de esta restricción es evitar la superposición de instancias. Por ejemplo, digamos que tuviste uninstance Fooable [Char] , y luego alguien vino y definió un instance Fooable [a]. Ahora el compilador no podrá averiguar cuál quería usar y le dará un error.

Al usar -XFlexibleInstances, básicamente le promete al compilador que no definirá ninguna de esas instancias.

Dependiendo de lo que esté tratando de lograr, podría ser mejor definir un contenedor:

newtype Wrapper = Wrapper String
instance Fooable Wrapper where
    ...
Hammar
fuente
4
Digamos por el bien del argumento que yo también quería instance Fooable [a]. ¿Hay alguna manera de hacer que la toFoofunción se comporte de manera diferente si aes un Char?
John F. Miller
7
@John: Hay una extensión -XOverlappingInstancesque permite esto y elige la instancia más específica. Consulte la guía del usuario de GHC para obtener más detalles .
Hammar
18

Te encuentras con dos limitaciones de las clases de tipos clásicas de Haskell98:

  • no permiten sinónimos de tipo en casos
  • no permiten tipos anidados que a su vez no contienen variables de tipo.

Estas onerosas restricciones se eliminan mediante dos extensiones de idioma:

  • -XTypeSynonymInstances

que le permite usar sinoyms de tipo (como Stringpara [Char]), y:

  • -XFlexibleInstances

que eliminan las restricciones sobre los tipos de instancia que tienen la forma T a b ..donde los parámetros son variables de tipo. La -XFlexibleInstancesbandera permite que el encabezado de la declaración de instancia mencione tipos anidados arbitrarios.

Tenga en cuenta que la eliminación de estas restricciones a veces puede llevar a instancias superpuestas , momento en el cual, es posible que se necesite una extensión de idioma adicional para resolver la ambigüedad, lo que permite que GHC elija una instancia por usted.


Referencias ::

Don Stewart
fuente
4

FlexibleInstances no es una buena respuesta en la mayoría de los casos. Mejores alternativas son envolver el String en un nuevo tipo o introducir una clase auxiliar como esta:

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

instance Element Char where
   listToFoo = FooString

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

Véase también: http://www.haskell.org/haskellwiki/List_instance

Leming
fuente
2

Además de estas respuestas, si no se siente cómodo levantando las restricciones, puede haber casos en los que tenga sentido envolver su String en un nuevo tipo, que puede ser una instancia de una clase. La compensación sería una posible fealdad, tener que envolver y desenvolver su código.

kowey
fuente