适用于记录的构造函数

Maa*_*ten 5 haskell applicative

我想一般为haskell记录创建应用程序构造函数,以便为记录创建解析器.

考虑记录:

data Record = Record {i :: Int, f :: Float}
Run Code Online (Sandbox Code Playgroud)

我想要的构造函数:

Record <$> pInt <*> pFloat
Run Code Online (Sandbox Code Playgroud)

给出了基本类型的解析器:

class Parseable a where
  getParser :: Parser a

instance Parseable Int where
  getParser = pInt

instance Parseable Float where
  getParser = pFloat
Run Code Online (Sandbox Code Playgroud)

有没有可以做到这一点的图书馆?是否可以为记录定义getParser?提前致谢.

Ste*_*ans 9

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

{-# LANGUAGE FlexibleContexts     #-}
{-# LANGUAGE FlexibleInstances    #-}
{-# LANGUAGE TypeFamilies         #-}
{-# LANGUAGE TypeOperators        #-}
{-# LANGUAGE UndecidableInstances #-}

import Control.Applicative
import Generics.Regular
Run Code Online (Sandbox Code Playgroud)

至少有两个最流行的解析器 - 组合器库带有一个applicative-functor接口:例如,参见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
Run Code Online (Sandbox Code Playgroud)

(注意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
Run Code Online (Sandbox Code Playgroud)

直截了当,我们有:

class Parseable a where
  getParser :: Parser a

instance Parseable Int where
  getParser = pInt

instance Parseable Float where
  getParser = pFloat
Run Code Online (Sandbox Code Playgroud)

并且,对于您的记录类型,根据需要:

data Record = Record {i :: Int, f :: Float}

instance Parseable Record where
  getParser = Record <$> pInt <* pSym ' ' <*> pFloat
Run Code Online (Sandbox Code Playgroud)

现在,我们如何通常生成这样的解析器?

首先,我们定义所谓的模式函子Record(详见常规文档):

type instance PF Record = K Int :*: K Float
Run Code Online (Sandbox Code Playgroud)

然后,我们创建Record一个类型类的实例Regular:

instance Regular Record where
  from (Record n r) = K n :*: K r
  to (K n :*: K r)  = Record n r
Run Code Online (Sandbox Code Playgroud)

接下来,我们定义一个通用解析器:

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

(要涵盖所有常规类型,您必须提供更多实例,但这些将适用于您的示例.)

现在,我们可以证明类中的每个类型Regular(给定ParseableF其模式函子的实例)都带有一个解析器:

instance (Regular a, ParseableF (PF a)) => Parseable a where
  getParser = to <$> getParserF getParser
Run Code Online (Sandbox Code Playgroud)

让我们来看看吧.删除原始实例Parseable(即,for Int,Float当然Record),并保留单个通用实例.开始了:

> runParser (getParser :: Parser Record) "42 3.14"
[(Record {i = 42, f = 3.14},"")]
Run Code Online (Sandbox Code Playgroud)

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