(通常)从自定义数据类型构建解析器?

use*_*201 4 haskell

我正在研究需要与服务器通信的网络流客户端.服务器以字节为单位对响应进行编码,例如"1\NULJohn\NULTeddy\NUL501\NUL",其中'\ NUL'是分隔符.上述响应转换为"这是类型1的消息(由服务器硬编码),它告诉客户端用户的ID是什么(这里,"John Teddy"的用户ID是"501").

天真地,我定义了一个自定义数据类型

data User
  { firstName :: String
  , lastName :: String
  , id :: Int
  }
Run Code Online (Sandbox Code Playgroud)

和这种数据类型的解析器

parseID :: Parser User
parseID = ...
Run Code Online (Sandbox Code Playgroud)

然后,在解析器成功匹配这样的响应之后,只需编写一个处理程序来完成某项工作(例如,写入数据库).这非常简单.

但是,服务器有几乎100种类型的不同响应,客户端需要解析.我怀疑必须有更优雅的方式来完成这项工作,而不是像这样编写100个几乎相同的解析器,因为毕竟所有haksell编码器都是懒惰的.我是泛型编程的新手,所以有人可以告诉我是否有可以完成这项工作的软件包?

dan*_*iaz 5

对于这些问题,我转向generics-sop而不是直接使用泛型.generics-sop构建在Generics之上,提供以统一方式操作记录中所有字段的功能.

在这个答案中,我使用了带有baseReadP解析器,但任何其他解析器都可以.一些初步进口:Applicative

{-# 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
Run Code Online (Sandbox Code Playgroud)

我们定义了一个类型类,它可以Applicative为每个实例生成一个解析器.这里我们只定义Int和的实例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
Run Code Online (Sandbox Code Playgroud)

现在我们为记录定义一个通用解析器,其中每个字段都有一个HasSimpleParser实例:

recParser :: (Generic r, Code r ~ '[xs], All HasSimpleParser xs) => ReadP r
recParser = to . SOP . Z <$> hsequence (hcpure (Proxy @HasSimpleParser) getSimpleParser)
Run Code Online (Sandbox Code Playgroud)

Code r ~ '[xs], All HasSimpleParser xs约束的意思是"这个类型只有一个构造函数,字段类型的列表xs,所有的字段类型有HasSimpleParser实例".

hcpure构造一个n-ary product(NP),其中每个组件都是相应字段的解析器r.(NP产品将每个组件包装在一个类型构造函数中,在我们的例子中是解析器类型ReadP).

然后我们使用hsequence将解析器的n-ary产品转换为n-ary产品的解析器.

最后,我们FMAP到生成的解析器和转n进制产品放回原来的r使用记录to.在ZSOP构造都需要转动n元产品进入加总产品to功能的期望.


好的,让我们定义一个示例记录并使其成为以下实例Generics.SOP.Generic:

data Foo = Foo { x :: Int, y :: Bool } deriving (Show, GHC.Generic)

instance Generic Foo -- Generic from generics-sop
Run Code Online (Sandbox Code Playgroud)

让我们来看看,如果我们可以解析FoorecParser:

main :: IO ()
main = do
    print $ readP_to_S (recParser @Foo) "55False"
Run Code Online (Sandbox Code Playgroud)

结果是

[(Foo {x = 55, y = False},"")]
Run Code Online (Sandbox Code Playgroud)