我想通常为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 :(得分:9)
这可以使用例如regular库来完成。使用此库通常需要一些语言扩展:
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE UndecidableInstances #-}
import Control.Applicative
import Generics.Regular
至少有两个最流行的解析器 - 组合器库带有一个applicative-functor接口:例如,请参阅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
的所谓模式函子(详见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,它对记录做了特别好的事情。你可能想先检查一下。此外,该库附带了Template Haskell支持,因此可以自动派生Regular
的实例。这些实例包括记录标签的特殊结构类型,因此您可以使用通用函数来处理记录类型。查看文档。
答案 1 :(得分:3)
尽管我喜欢regular
包,但我想指出,因为ghc-7.2
GHC内置支持导出通用表示类型,所以你不必依赖模板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},"")]