这可以使用例如常规库来完成。使用这个库通常需要一些语言扩展:
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE UndecidableInstances #-}
import Control.Applicative
import Generics.Regular
至少有两个最流行的解析器组合库带有一个应用函子接口:例如,参见 uu-parsinglib和parsec,但是为了简单起见,让我们在这里使用简单的成功列表解析器。
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
(请注意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
直截了当,我们有:
class Parseable a where
getParser :: Parser a
instance Parseable Int where
getParser = pInt
instance Parseable Float where
getParser = pFloat
并且,对于您的记录类型,根据需要:
data Record = Record {i :: Int, f :: Float}
instance Parseable Record where
getParser = Record <$> pInt <* pSym ' ' <*> pFloat
现在,我们一般如何生成这样的解析器?
首先,我们定义所谓的模式函子(详见正则Record
的文档):
type instance PF Record = K Int :*: K Float
然后,我们创建Record
一个类型类的实例Regular
:
instance Regular Record where
from (Record n r) = K n :*: K r
to (K n :*: K r) = Record n r
接下来,我们定义一个通用解析器:
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
(要涵盖所有常规类型,您将必须提供更多实例,但这些将适用于您的示例。)
现在,我们可以证明类中的每种类型Regular
(给定ParseableF
其模式函子的实例)都带有一个解析器:
instance (Regular a, ParseableF (PF a)) => Parseable a where
getParser = to <$> getParserF getParser
让我们试一试。删除 的原始实例Parseable
(即 、Int
和Float
当然Record
的实例),只保留单个通用实例。开始了:
> runParser (getParser :: Parser Record) "42 3.14"
[(Record {i = 42, f = 3.14},"")]
注意:这只是一个非常基本的示例,说明如何使用常规库派生通用解析器。库本身带有一个通用的成功列表解析器,它对记录做了特别好的事情。您可能想先检查一下。此外,该库带有 Template Haskell 支持,因此Regular
可以自动派生实例。这些实例包括记录标签的特殊结构类型,因此您可以让您的通用函数真正花哨地处理记录类型。查看文档。