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 ?提前致谢。

4

2 回答 2

9

这可以使用例如常规库来完成。使用这个库通常需要一些语言扩展:

{-# 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的文档):

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(即 、IntFloat当然Record的实例),只保留单个通用实例。开始了:

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

注意:这只是一个非常基本的示例,说明如何使用常规库派生通用解析器。库本身带有一个通用的成功列表解析器,它对记录做了特别好的事情。您可能想先检查一下。此外,该库带有 Template Haskell 支持,因此Regular可以自动派生实例。这些实例包括记录标签的特殊结构类型,因此您可以让您的通用函数真正花哨地处理记录类型。查看文档。

于 2012-07-10T15:54:38.220 回答
3

尽管我很喜欢这个regular包,但我想指出,由于ghc-7.2GHC 具有对派生泛型表示类型的内置支持,因此您不必依赖 Template Haskell 来执行此操作。

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

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

import Control.Applicative
import GHC.Generics

您仍然Parser如上所述定义及其实例。您需要Generic为您的Record类型派生类:

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

该类与该类Generic非常相似Regular。您不必现在定义PF或实例Regular

取而代之的是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},"")]
于 2012-07-11T15:11:34.263 回答