(通常)从自定义数据类型构建解析器? [英] (Generically) Build Parsers from custom data types?

查看:80
本文介绍了(通常)从自定义数据类型构建解析器?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在研究需要与服务器通话的网络流客户端。服务器以字节串编码响应,例如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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆