2012-07-10 187 views
5

我想一般创建haskell记录的应用构造函数,以创建记录的解析器。适用于记录的构造函数

考虑记录:

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

我想构造:

基本类型
Record <$> pInt <*> pFloat 

解析器给出:

class Parseable a where 
    getParser :: Parser a 

instance Parseable Int where 
    getParser = pInt 

instance Parseable Float where 
    getParser = pFloat 

是否有已经可以做到这一点任何库?是否可以定义getParser作为记录?提前致谢。

+0

只是为了澄清:你想为'可解析Record'一个实例为您生成? – kosmikus 2012-07-10 15:35:08

回答

9

这可以使用例如regular库来完成。与此库的工作通常需要一些语言扩展:

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

import Control.Applicative 
import Generics.Regular 

至少有两个最流行的解析器组合子库配备了应用性,函子接口:见,例如,uu-parsinglibparsec,而是让事情变得容易,让我们在这里使用简单的成功列表解析器。

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所谓的图案仿函数(见regular获取细节):

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},"")] 

注:这只是一个如何得到使用常规库通用解析器非常简单的例子。图书馆本身带有一个generic list-of-successes parser,记录特别好。你可能想先检查一下。此外,该库还提供了模板Haskell支持,以便可以自动导出Regular的实例。这些实例包括记录标签的特殊结构类型,以便您可以让您的泛型函数将记录类型处理为真正的幻想。查看文档。

3

虽然我很喜欢regular包,我想指出的是,由于ghc-7.2的GHC已经内置了支持获得通用表示类型,这样你就不必依赖模板哈斯克尔做到这一点。

与dblhelix建议的解决方案相比的更改如下。你需要稍微不同的标志和模块的输入:

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

import Control.Applicative 
import GHC.Generics 

你仍然定义Parser及其实例如上。 您需要派生类GenericRecord类型:

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

Generic是非常相似的类Regular。您现在不必定义PFRegular的实例。

相反的ParseableF,我们定义一个类Parseable'这是在风格上非常相似,但有一点点不同:

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' 

最后,Parseable,我们定义了一个通用的默认方法:

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 

现在,使得可解析类型为Record与提供空实例声明一样简单:

instance Parseable Record 

的例子可以作为前面:

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