2012-07-10 13 views
5

Quiero crear genéricamente constructores aplicativos para registros de haskell para crear un analizador sintáctico para el registro.Constructor aplicativo para registros

considerará el expediente:

data Record = Record {i :: Int, f :: Float} 

el constructor que quiero: se dan

Record <$> pInt <*> pFloat 

analizadores para tipos básicos:

class Parseable a where 
    getParser :: Parser a 

instance Parseable Int where 
    getParser = pInt 

instance Parseable Float where 
    getParser = pFloat 

¿Hay bibliotecas que ya pueden hacer esto ? ¿Es posible definir getParser para un registro? Gracias por adelantado.

+0

Solo para aclarar: desea una instancia de 'parseable Record' que se generará para usted? – kosmikus

Respuesta

9

Esto se puede hacer utilizando, por ejemplo, la biblioteca regular. Trabajar con esta biblioteca generalmente requiere algunas extensiones de lenguaje:

{-# LANGUAGE FlexibleContexts  #-} 
{-# LANGUAGE FlexibleInstances #-} 
{-# LANGUAGE TypeFamilies   #-} 
{-# LANGUAGE TypeOperators  #-} 
{-# LANGUAGE UndecidableInstances #-} 

import Control.Applicative 
import Generics.Regular 

Al menos dos de las bibliotecas analizador-combinator más populares vienen con una interfaz aplicativa-funtor: véase, por ejemplo, uu-parsinglib y parsec, sino para mantener las cosas fáciles , usemos analizadores de lista de éxitos simples aquí.

newtype Parser a = Parser {runParser :: ReadS a} 

instance Functor Parser where 
    fmap f p = Parser $ \s -> [(f x, s') | (x, s') <- runParser p s] 

instance Applicative Parser where 
    pure x = Parser $ \s -> [(x, s)] 
    p <*> q = Parser $ \s -> 
    [(f x, s'') | (f, s') <- runParser p s, (x, s'') <- runParser q s'] 

instance Alternative Parser where 
    empty = Parser $ \_ -> [] 
    p <|> q = Parser $ \s -> runParser p s ++ runParser q s 

(. Tenga en cuenta que type ReadS a = String -> [(a, String)])

pSym :: Char -> Parser Char 
pSym c = Parser $ \s -> case s of 
    (c' : s') | c == c' -> [(c', s')] 
    _     -> [] 

pInt :: Parser Int 
pInt = Parser reads 

pFloat :: Parser Float 
pFloat = Parser reads 

rodeos, tenemos:

class Parseable a where 
    getParser :: Parser a 

instance Parseable Int where 
    getParser = pInt 

instance Parseable Float where 
    getParser = pFloat 

Y, para su tipo de registro, según se desee:

data Record = Record {i :: Int, f :: Float} 

instance Parseable Record where 
    getParser = Record <$> pInt <* pSym ' ' <*> pFloat 

Ahora ¿Cómo podemos genéricamente? generar tal analizador?

En primer lugar, se define el llamado funtor patrón de Record (consulte la documentación de regular para más detalles):

type instance PF Record = K Int :*: K Float 

A continuación, hacemos Record una instancia de la clase de tipo Regular:

instance Regular Record where 
    from (Record n r) = K n :*: K r 
    to (K n :*: K r) = Record n r 

a continuación, se define un analizador genérico:

class ParseableF f where 
    getParserF :: Parser a -> Parser (f a) 

instance ParseableF (K Int) where 
    getParserF _ = K <$> pInt 

instance ParseableF (K Float) where 
    getParserF _ = K <$> pFloat 

instance (ParseableF f, ParseableF g) => ParseableF (f :*: g) where 
    getParserF p = (:*:) <$> getParserF p <* pSym ' ' <*> getParserF p 

(Para cubrir todo tipo regulares, que tendrá que proporcionar algunos más casos, pero estos va a hacer por tu ejemplo.)

Ahora, podemos demostrar que cada tipo en la clase Regular (dada una instancia ParseableF de su función de patrón) viene con un analizador:

instance (Regular a, ParseableF (PF a)) => Parseable a where 
    getParser = to <$> getParserF getParser 

Démosle un giro. Elimine las instancias originales de Parseable (es decir, las de Int, Float y, por supuesto, Record) y solo conserve la única instancia genérica.Aquí vamos:

> runParser (getParser :: Parser Record) "42 3.14" 
[(Record {i = 42, f = 3.14},"")] 

Nota: esto es sólo un ejemplo muy básico de cómo derivar analizadores genéricos que utilizan la biblioteca regular. La biblioteca viene con un generic list-of-successes parser que hace cosas particularmente agradables con los registros. Es posible que desee verificar ese primero. Además, la biblioteca viene con el soporte de Template Haskell para que las instancias de Regular puedan derivarse automáticamente. Estas instancias incluyen tipos de estructura especial para etiquetas de registro, de modo que puede hacer que sus funciones genéricas traten los tipos de registros realmente extravagantes. Mira los documentos.

3

Tanto como me gusta el paquete regular, quiero señalar que desde ghc-7.2 el GHC tiene soporte integrado para derivar tipos de representación genéricos, por lo que no tiene que depender de Template Haskell para hacerlo.

Los cambios en comparación con la solución sugerida por dblhelix son los siguientes. Es necesario ligeramente diferentes banderas y módulos importados:

{-# LANGUAGE DeriveGeneriC#-} 
{-# LANGUAGE DefaultSignatures #-} 
{-# LANGUAGE FlexibleContexts #-} 
{-# LANGUAGE TypeOperators #-} 

import Control.Applicative 
import GHC.Generics 

Todavía se define Parser y sus instancias que el anterior. Es necesario derivar la clase Generic para su tipo Record:

data Record = Record { i :: Int, f :: Float } 
    deriving (Generic, Show) 

La clase Generic es muy similar a la clase Regular. No tiene que definir PF o una instancia de Regular ahora.

En lugar de ParseableF, definimos una clase Parseable' que es muy similar en estilo, pero muy ligeramente diferente:

class Parseable' f where 
    getParser' :: Parser (f a) 

-- covers base types such as Int and Float: 
instance Parseable a => Parseable' (K1 m a) where 
    getParser' = K1 <$> getParser 

-- covers types with a sequence of fields (record types): 
instance (Parseable' f, Parseable' g) => Parseable' (f :*: g) where 
    getParser' = (:*:) <$> getParser' <* pSym ' ' <*> getParser' 

-- ignores meta-information such as constructor names or field labels: 
instance Parseable' f => Parseable' (M1 m l f) where 
    getParser' = M1 <$> getParser' 

Por último, para Parseable, definimos un método genérico predeterminado:

class Parseable a where 
    getParser :: Parser a 
    default getParser :: (Generic a, Parseable' (Rep a)) => Parser a 
    getParser = to <$> getParser' 

instance Parseable Int where 
    getParser = pInt 

instance Parseable Float where 
    getParser = pFloat 

Ahora, hacer que el tipo Record sea parseable es tan simple como proporcionar una declaración de instancia vacía:

instance Parseable Record 

El ejemplo funciona como antes:

> runParser (getParser :: Parser Record) "42 3.14" 
[(Record {i = 42, f = 3.14},"")] 
Cuestiones relacionadas