(通常)从自定义数据类型构建解析器? [英] (Generically) Build Parsers from custom data types?
问题描述
我正在研究需要与服务器通话的网络流客户端。服务器以字节串编码响应,例如1 \NULJohn\NULTeddy\NUL501\NUL,其中'\NUL'是分隔符。上述响应转换为这是类型1(由服务器硬编码)的消息,其告诉客户端用户的ID是什么(这里,John Teddy的用户ID是501)。
如此天真地定义一个自定义数据类型
pre $ data $ User
{firstName :: String
,lastName :: String
,id :: Int
}
和这个数据类型的解析器
parseID :: Parser User
parseID = ...
然后我们写一个处理程序来完成一些工作(例如写入数据库)然后,服务器有几乎100种不同的响应类似于客户端需要解析的这种类型的响应。
。我怀疑必须有一种更优雅的方式来完成这项工作,而不是像这样编写100个几乎完全相同的解析器,因为毕竟,所有haksell编码器都是懒惰的。对于泛型编程来说是一个新手,所以有人可以告诉我,是否有一个可以完成这个工作的包?我转向泛型-sop ,而不是直接使用泛型。 generics-sop 建立在泛型的基础之上,它提供了以统一的方式处理记录中的所有字段的功能。
在这个答案中我使用 ReadP 解析器,它随 base 出现,但任何其他的 Applicative
解析器都可以。一些初步导入:
{ - #language DeriveGeneric# - }
{ - #language FlexibleContexts# - }
{ - #language FlexibleInstances# - }
{ - #language TypeFamilies# - }
{ - #language DataKinds# - }
{ - #language TypeApplications# - } - for代理
import Text.ParserCombinators.ReadP(ReadP,readP_to_S)
import Text.ParserCombinators.ReadPrec(readPrec_to_P)
import Text.Read(readPrec)
import Data .Proxy
导入合格的GHC.Generics作为GHC
导入Generics.SOP
我们定义了一个可为每个实例生成 Applicative
解析器的类型类。这里我们只定义 Int
和 Bool
的实例:
class HasSimpleParser c其中
getSimpleParser :: ReadP c
实例HasSimpleParser Int其中
getSimpleParser = readPrec_to_P readPrec 0
实例HasSimpleParser Bool其中
getSimpleParser = readPrec_to_P readPrec 0
现在我们定义一个通用每个字段都有一个 HasSimpleParser
实例的记录解析器:
recParser ::(Generic r,Code r〜'[xs],All HasSimpleParser xs)=> ReadP r
recParser = to。 SOP。 Z< $> hsequence(代理@HasSimpleParser)getSimpleParser)
代码r'' [xs],所有HasSimpleParser xs 约束表示此类型只有一个构造函数,字段类型列表是 xs
,并且所有字段类型有 HasSimpleParser
个实例。
hcpure
构造一个n-ary产品( NP
),其中每个组件都是相应字段 r
的解析器。 ( NP
产品将每个组件包装在一个类型构造器中,在我们的例子中是解析器类型 ReadP
)。 p>
然后我们使用 hsequence
可将解析器的n元产物转换为n元产品的解析器。
最后,我们将fmap导入解析器,然后使用 r 记录=http://hackage.haskell.org/package/generics-sop-0.2.4.0/docs/Generics-SOP.html#v:to =nofollow noreferrer> to
。 Z
和 SOP
构造函数是将n元产品转换为产品和<&c $ c>到函数期望。
好的,让我们定义一个示例记录并使其成为 Generics.SOP.Generic
:
data Foo = Foo {x: :Int,y :: Bool}派生(Show,GHC.Generic)
实例Generic Foo - Generic的泛型
让我们来看看是否可以用 recParser
来解析 Foo
:
main :: IO()
main = do
print $ readP_to_S(recParser @Foo)55 False
结果是
[(Foo { x = 55,y = False},)]
I'm working on a network streaming client that needs to talk to the server. The server encodes the responses in bytestrings, for example, "1\NULJohn\NULTeddy\NUL501\NUL", where '\NUL' is the separator. The above response translates to "This is a message of type 1(hard coded by the server), which tells the client what the ID of a user is(here, the user id of "John Teddy" is "501").
So naively I define a custom data type
data User
{ firstName :: String
, lastName :: String
, id :: Int
}
and a parser for this data type
parseID :: Parser User
parseID = ...
Then one just writes a handler to do some job(e.g., write to a database) after the parser succesfully mathes a response like this. This is very straightforward.
However, the server has almost 100 types of different responses like this that the client needs to parse. I suspect that there must be a much more elegant way to do the job rather than writing 100 almost identical parsers like this, because, after all, all haksell coders are lazy. I am a total newbie to generic programming so can some one tell me if there is a package that can do this job?
For these kinds of problems I turn to generics-sop instead of using generics directly. generics-sop is built on top of Generics and provides functions for manipulating all the fields in a record in a uniform way.
In this answer I use the ReadP parser which comes with base, but any other Applicative
parser would do. Some preliminary imports:
{-# language DeriveGeneric #-}
{-# language FlexibleContexts #-}
{-# language FlexibleInstances #-}
{-# language TypeFamilies #-}
{-# language DataKinds #-}
{-# language TypeApplications #-} -- for the Proxy
import Text.ParserCombinators.ReadP (ReadP,readP_to_S)
import Text.ParserCombinators.ReadPrec (readPrec_to_P)
import Text.Read (readPrec)
import Data.Proxy
import qualified GHC.Generics as GHC
import Generics.SOP
We define a typeclass that can produce an Applicative
parser for each of its instances. Here we define only the instances for Int
and Bool
:
class HasSimpleParser c where
getSimpleParser :: ReadP c
instance HasSimpleParser Int where
getSimpleParser = readPrec_to_P readPrec 0
instance HasSimpleParser Bool where
getSimpleParser = readPrec_to_P readPrec 0
Now we define a generic parser for records in which every field has a HasSimpleParser
instance:
recParser :: (Generic r, Code r ~ '[xs], All HasSimpleParser xs) => ReadP r
recParser = to . SOP . Z <$> hsequence (hcpure (Proxy @HasSimpleParser) getSimpleParser)
The Code r ~ '[xs], All HasSimpleParser xs
constraint means "this type has only one constructor, the list of field types is xs
, and all the field types have HasSimpleParser
instances".
hcpure
constructs an n-ary product (NP
) where each component is a parser for the corresponding field of r
. (NP
products wrap each component in a type constructor, which in our case is the parser type ReadP
).
Then we use hsequence
to turn a n-ary product of parsers into the parser of an n-ary product.
Finally, we fmap into the resulting parser and turn the n-ary product back into the original r
record using to
. The Z
and SOP
constructors are required for turning the n-ary product into the sum-of-products the to
function expects.
Ok, let's define an example record and make it an instance of Generics.SOP.Generic
:
data Foo = Foo { x :: Int, y :: Bool } deriving (Show, GHC.Generic)
instance Generic Foo -- Generic from generics-sop
Let's check if we can parse Foo
with recParser
:
main :: IO ()
main = do
print $ readP_to_S (recParser @Foo) "55False"
The result is
[(Foo {x = 55, y = False},"")]
这篇关于(通常)从自定义数据类型构建解析器?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!