2012-10-04 66 views
56

Eeek! ¡GHCi encontró Skolems en mi código!¿Qué son skolems?

... 
Couldn't match type `k0' with `b' 
    because type variable `b' would escape its scope 
This (rigid, skolem) type variable is bound by 
    the type signature for 
    groupBy :: Ord b => (a -> b) -> Set a -> Set (b, [a]) 
The following variables have types that mention k0 
... 

¿Qué son? ¿Qué es lo que quieren con mi programa? ¿Y por qué están tratando de escapar (los pequeños ingratos blighters)?

+0

http://hackage.haskell.org/trac/ghc/ticket/7194? –

+0

Sí, lo vi, pero ese boleto no da mucha explicación sobre lo que es un skolem. –

+2

Estoy buscando una explicación de qué skolems son y qué los causa, en lugar de específicamente cómo arreglar mi código. Ya arreglé mi código, pero no estoy muy seguro de por qué lo que hice hizo que los skolems se fueran ... –

Respuesta

42

Para empezar, una variable de tipo "rígido" en un contexto significa una variable de tipo ligada por un cuantificador fuera de ese contexto, que por lo tanto no se puede unificar con otras variables de tipo.

Esto funciona en gran medida como variables vinculadas por una lambda: dado un lambda (\x -> ...), desde el "exterior" puede aplicarlo a cualquier valor que desee, por supuesto; pero en el interior, no puede simplemente decidir que el valor de x debe ser de algún valor particular. Escoger un valor para x dentro de la lambda debería sonar bastante tonto, pero eso es lo que significan los errores sobre "no puede coincidir bla, bla, variable de tipo rígido, bla, bla".

Tenga en cuenta que, incluso sin el uso de cuantificadores explícitos forall, cualquier firma de tipo de nivel superior tiene implícita forall para cada variable de tipo mencionado.

Por supuesto, ese no es el error que está recibiendo. Lo que significa una "variable de tipo de escape" es incluso más tonto: es como tener un lambda (\x -> ...) e intentar usar valores específicos de xfuera de lambda, independientemente de aplicarlo a un argumento. No, no aplicar el lambda a algo y usar el valor del resultado - me refiero en realidad a usar la variable misma fuera del alcance donde está definida.

La razón por la que esto puede suceder con los tipos (sin parecer tan obviamente absurdo como el ejemplo con una lambda) es porque hay dos nociones de "variables de tipo" flotando: Durante la unificación, tiene "variables" que representan tipos indeterminados, que luego se identifican con otras variables de ese tipo a través de la inferencia de tipo. Por otro lado, tiene las variables de tipo cuantificadas descritas anteriormente que se identifican específicamente como variables sobre los tipos posibles.

Considere el tipo de la expresión lambda (\x -> x). Comenzando desde un tipo completamente indeterminado a, vemos que se necesita un argumento y se lo reduce a a -> b, luego vemos que debe devolver algo del mismo tipo que su argumento, por lo que lo reducimos más a a -> a. Pero ahora funciona para cualquier tipo a que desee, por lo que le damos un cuantificador (forall a. a -> a).

Por lo tanto, una variable de tipo escapado ocurre cuando usted tiene un tipo ligado por un cuantificador que infiere GHC deberían unificarse con un tipo indeterminado fuera del alcance de este cuantificador.


Parece ser que olvidé explicar el término "skolem type variable" aquí, heh. Como se menciona en los comentarios, en nuestro caso es esencialmente sinónimo de "variable de tipo rígida", por lo que lo anterior aún explica la idea.

no estoy del todo seguro de que el término se originó a partir, pero me imagino que implica Skolem normal form y representando existencial cuantificación en términos de universales, como se hace en GHC. Una variable de tipo skolem (o rígida) es aquella que, dentro de algún alcance, tiene un tipo desconocido pero específico por alguna razón, que es parte de un tipo polimórfico, proveniente de un tipo de datos existenciales, & c.

+6

..pero ¿qué es una variable de tipo skolem? – AndrewC

+0

@AndrewC Según tengo entendido por el comentario de SPJ [aquí] (http://hackage.haskell.org/trac/ghc/ticket/4499), "skolem" == "rígido" en este contexto. –

+0

@AndrewC: Jajaja, revisé toda esa explicación, pero me olvidé de volver a definir el término en cuestión. De todos modos, como dice Mikhail, en este contexto significa lo mismo que "rígido". –

20

lo que tengo entendido, una "variable Skolem" es una variable que no coincida con ninguna otra variable, incluyendo en sí.

Esto parece surgir en Haskell cuando se utiliza características como foralls explícitas, GADTs, y otras extensiones del sistema tipo.

Por ejemplo, considere el tipo siguiente:

data AnyWidget = forall x. Widget x => AnyWidget x 

Lo que esto dice es que se puede tomar cualquier tipo que implementa la clase Widget, y se envuelve en un tipo AnyWidget. Ahora, supongamos que intenta Separar esto:

unwrap (AnyWidget w) = w 

Um, no, no se puede hacer eso. Porque, en tiempo de compilación, no tenemos idea de qué tipo tiene w, por lo que no hay forma de escribir una firma de tipo correcta para esto. Aquí el tipo de w ha "escapado" de AnyWidget, lo cual no está permitido.

Según tengo entendido, internamente GHC da w un tipo que es una variable Skolem, para representar el hecho de que no debe escapar. (Este no es el único escenario, hay un par de otros lugares donde un cierto valor no puede escapar debido a problemas de tipeo).

+15

No estoy de acuerdo con el "incluyéndose a sí mismo". Una variable de Skolem (o quizás una mejor constante de Skolem) representa un tipo fijo desconocido durante la inferencia de tipo. Como tal, una constante Skolem se empareja a sí misma, así como una variable (de unificación), pero no coincidirá con ningún tipo concreto. Las constantes skolem en realidad surgen de enlaces existenciales, por lo general. Son bastante diferentes de las variables de unificación normales que surgen de enlaces universales y coinciden con cualquier tipo concreto. – kosmikus

+0

@kosmikus No soy de ninguna manera un experto en las intrigas de cómo funciona realmente la inferinción tipo. Su explicación parece lógicamente autoconsistente, así que ... – MathematicalOrchid

+3

El punto de kosmikus se demuestra por la forma en que 'datos AnyEq = forrall a. Eq a => AE a' permite la definición 'reflexiva (AE x) = x == x'. La llamada a '==' es válida porque 'x' es del mismo tipo que ella, aunque no sabemos cuál es ese tipo. –

6

El mensaje de error aparece cuando una variable de tipo intenta escapar de su alcance.

Me tomó un tiempo para averiguar esto, así que voy a escribir un ejemplo.

{-# LANGUAGE ExistentialQuantification #-} 
data I a = I a deriving (Show) 
data SomeI = forall a. MkSomeI (I a) 

Entonces, si tratamos de escribir una función

unI (MkSomeI i) = i 

GHC se niega a escribir-inferir/tipo de la marca de esta función.


¿Por qué? Vamos a tratar de inferir el tipo de nosotros mismos:

  • unI es una definición lambda, por lo que es tipo es x -> y para algunos tipos x y y.
  • MkSomeI tiene un tipo forall a. I a -> SomeI
    • MkSomeI i tiene un tipo SomeI
    • i en los LHS tiene un tipo I z para algún tipo z. Debido al cuantificador forall, tuvimos que introducir una nueva variable de tipo (reciente). Tenga en cuenta que no es universal, ya que está dentro de la expresión (SomeI i).
    • así podemos unificar el tipo de variable x con SomeI, esto está bien. Entonces, el unI debería tener el tipo SomeI -> y.
  • i en el lado derecho por lo tanto tienen demasiado I z tipo.
  • En este punto, unificador intenta unificar y y I z, pero se da cuenta de que z se presenta en el contexto inferior. Por lo tanto, falla.

De lo contrario el tipo de unI tendría forall z. SomeI -> I z tipo, pero el correcto es exists z. SomeI -> I z. Sin embargo, ese único GHC no puede representar directamente.


Del mismo modo, podemos ver por qué

data AnyEq = forall a. Eq a => AE a 
-- reflexive :: AnyEq -> Bool 
reflexive (AE x) = x == x 

obras.

La variable (existencial) dentro de AE x no escapa al ámbito externo, por lo que todo está bien.


También me encontré con un "feature" en GHC 7.8.4 y 7.10.1, donde RankNTypes en sí está bien, pero la adición de GADTs provoca el error

{-# LANGUAGE RankNTypes #-} 
{-# LANGUAGE GADTs #-} 

example :: String -> I a -> String 
example str x = withContext x s 
    where 
    s i = "Foo" ++ str 

withContext :: I a -> (forall b. I b -> c) -> c 
withContext x f = f x 

lo que podría ser nada malo con su código . Podría ser GHC, que no puede resolver todo de manera consistente.

EDIT: La solución es dar un tipo a s :: forall a. I a -> String.

GADTs su vez en MonoLocalBinds, lo que hace que el tipo inferido de s tenga variable de Skolem, por lo que el tipo no es forall a. I a -> String, pero t -> String, eran t consigue atado en el contexto equivocado. Ver: https://ghc.haskell.org/trac/ghc/ticket/10644