2011-03-22 16 views
15

¿Hay alguna manera de obtener programáticamente una lista de instancias de una clase de tipo?Obtenga una lista de las instancias en una clase de tipo en Haskell

Me parece que el compilador debe conocer esta información para poder verificar y compilar el código, por lo que hay alguna forma de decirle al compilador: hey, conoces las instancias de esa clase, por favor pon una lista de ellas aquí mismo (como cadenas o cualquier representación de ellos).

+0

En realidad, el compilador no sabe qué instancias están definidas. Lo único que puede hacer es verificar si un tipo es instancia de una clase de tipo. – fuz

Respuesta

12

Puede generar las instancias en el alcance para una clase de tipo dada utilizando Template Haskell.

import Language.Haskell.TH 

-- get a list of instances 
getInstances :: Name -> Q [ClassInstance] 
getInstances typ = do 
    ClassI _ instances <- reify typ 
    return instances 

-- convert the list of instances into an Exp so they can be displayed in GHCi 
showInstances :: Name -> Q Exp 
showInstances typ = do 
    ins <- getInstances typ 
    return . LitE . stringL $ show ins 

La ejecución de este en GHCi:

*Main> $(showInstances ''Num) 
"[ClassInstance {ci_dfun = GHC.Num.$fNumInteger, ci_tvs = [], ci_cxt = [], ci_cls = GHC.Num.Num, ci_tys = [ConT GHC.Integer.Type.Integer]},ClassInstance {ci_dfun = GHC.Num.$fNumInt, ci_tvs = [], ci_cxt = [], ci_cls = GHC.Num.Num, ci_tys = [ConT GHC.Types.Int]},ClassInstance {ci_dfun = GHC.Float.$fNumFloat, ci_tvs = [], ci_cxt = [], ci_cls = GHC.Num.Num, ci_tys = [ConT GHC.Types.Float]},ClassInstance {ci_dfun = GHC.Float.$fNumDouble, ci_tvs = [], ci_cxt = [], ci_cls = GHC.Num.Num, ci_tys = [ConT GHC.Types.Double]}]" 

Otra técnica útil está mostrando todos los casos en el alcance para una clase de tipo dado usando GHCi.

Prelude> :info Num 
class (Eq a, Show a) => Num a where 
    (+) :: a -> a -> a 
    (*) :: a -> a -> a 
    (-) :: a -> a -> a 
    negate :: a -> a 
    abs :: a -> a 
    signum :: a -> a 
    fromInteger :: Integer -> a 
    -- Defined in GHC.Num 
instance Num Integer -- Defined in GHC.Num 
instance Num Int -- Defined in GHC.Num 
instance Num Float -- Defined in GHC.Float 
instance Num Double -- Defined in GHC.Float 

Editar: La cosa importante a saber es que el compilador sólo es consciente de las clases de tipos en su alcance en cualquier módulo dado (o en el indicador de ghci, etc.). Por lo tanto, si llama a la función showInstances TH sin importar, solo obtendrá instancias del Preludio. Si tiene otros módulos en el alcance, p. Data.Word, entonces verá todas esas instancias también.

+1

Parece que esto solo funciona para GHC 7 y TH 2.5. Intentar hacer esto en TH 2.4.0.1 no parece darme lo que quiero. ¿Es esta una nueva característica entonces? – mentics

+0

Estoy ejecutando GHC 7.6.3. Aquí hay una actualización de la respuesta: '[ClassInstance]' ahora es '[InstanceDec]'. Además, necesitaba ejecutar ': set -XTemplateHaskell' en GHCi para duplicar el ejemplo. – apolune

2

Supongo que no es posible. Le explico la implementación de las clases de tipos (para GHC), de ellas puede ver que el compilador no necesita saber qué tipos son instancias de una clase de tipos. Solo debe saber si un tipo específico es instancia o no.

Una clase de tipo se traducirá en un tipo de datos. Como ejemplo, tomemos Eq:

class Eq a where 
    (==),(/=) :: a -> a -> Bool 

La clase de tipos se traducirá en una especie de diccionario, que contiene todas sus funciones:

data Eq a = Eq { 
    (==) :: a -> a -> Bool, 
    (/=) :: a -> a -> Bool 
    } 

Cada restricción clase de tipos se traduce en un argumento extra que contiene el diccionario:

elem :: Eq a => a -> [a] -> Bool 
elem _ [] = False 
elem a (x:xs) | x == a = True 
       | otherwise = elem a xs 

se convierte en:

elem :: Eq a -> a -> [a] -> Bool 
elem _ _ [] = False 
elem eq a (x:xs) | (==) eq x a = True 
       | otherwise = elem eq a xs 

Lo importante es que el diccionario se aprobará en tiempo de ejecución. Imagina, tu proyecto contiene muchos módulos. GHC no tiene que verificar todos los módulos por instancias, solo tiene que buscar, ya sea que una instancia esté definida en algún lugar.

Pero si tiene la fuente disponible, creo que un antiguo grep para las instancias sería suficiente.

+1

No es exactamente cierto. El compilador solo necesita saber si un tipo específico es una instancia, sin embargo, en cualquier momento el compilador puede enumerar todas las instancias en el alcance y generar la lista deseada. GHCi hará esto si usa ': info Num' por ejemplo. –

0

No es posible hacer esto automáticamente para las clases existentes. Para su propia clase y ejemplos de eso, podría hacerlo. Debería declarar todo a través de Template Haskell (o quizás el cuasi-presupuesto) y generaría automáticamente una extraña estructura de datos que codifica las instancias declaradas. Definir la extraña estructura de datos y hacer que Template Haskell haga esto son detalles que quedan para cualquiera que tenga un caso de uso para ellos.

Quizás podría agregar algo de Template Haskell u otra magia a su compilación para incluir todos los archivos fuente como texto disponible en tiempo de ejecución (c.f. programa quine). A continuación, el programa sería 'grep sí' ...

6

Esto va a ejecutar en una gran cantidad de problemas tan pronto como se obtiene declaraciones de instancias como

instance Eq a => Eq [a] where 
    [] == [] = True 
    (x:xs) == (y:ys) = x == y && xs == ys 
    _ == _ = False 

y

instance (Eq a,Eq b) => Eq (a,b) where 
    (a1,b1) == (a2,b2) = a1 == a2 && b1 == b2 

junto con una única instancia concreta (por ejemplo, instance Eq Bool).

que obtendrá una lista infinita de instancias para Eq-Bool, [Bool], [[Bool]], [[[Bool]]] y así sucesivamente, (Bool,Bool), ((Bool,Bool),Bool), (((Bool,Bool),Bool),Bool) etcétera, junto con diversas combinaciones de éstos, tales como ([((Bool,[Bool]),Bool)],Bool) y así sucesivamente.No está claro cómo representarlos en un String; incluso una lista de TypeRep requeriría una enumeración bastante inteligente.

El compilador puede (intentar) deducir si un tipo es una instancia de Eq para cualquier tipo dado, pero no ha leído en todas las declaraciones de instancias en el perímetro y después apenas comienza deduciendo todos los casos posibles, ya eso nunca terminará!

La pregunta importante es, por supuesto, ¿para qué necesita esto?

Cuestiones relacionadas