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.
Solo para aclarar: desea una instancia de 'parseable Record' que se generará para usted? – kosmikus