Parsec置换解析

时间:2016-12-09 20:08:27

标签: haskell parsec

我编写了这样的排列解析示例:

data Entry = Entry {
    first_name :: String
  , last_name :: String
  , date_of_birth :: Maybe String
  , nationality :: Maybe String
  , parentage :: Maybe String
} deriving (Show)

nameParser :: Parser (String, String)
nameParser = do
  first_name <- many1 upper
  endOfLine
  last_name <- many1 letter
  endOfLine
  return $ (first_name, last_name)

attributeParser :: String -> Parser String
attributeParser field = do
  string $ field ++ ": "
  value <- many1 (noneOf "\n")
  endOfLine
  return value

entryParser :: Parser Entry
entryParser = do
  (f, l) <- nameParser
  (d, n, p) <- permute ((,,)
    <$?> (Nothing, liftM Just (try $ attributeParser "Date of Birth"))
    <|?> (Nothing, liftM Just (try $ attributeParser "Nationality"))
    <|?> (Nothing, liftM Just (try $ attributeParser "Parentage"))
    )
  return $ Entry f l d n p

main = do
    mapM_ putStrLn . map (show . parse entryParser "") $ goodTests

goodTests =
  "AAKVAAG\nTorvild\nDate of Birth: 1 July\nNationality: Norwegian\nParentage: business executive\n" :
  "AAKVAAG\nTorvild\nNationality: Norwegian\nParentage: business executive\n" :
  "AAKVAAG\nTorvild\nParentage: business executive\nNationality: Norwegian\n" :
  "AAKVAAG\nTorvild\nParentage: business executive\n" :
  "AAKVAAG\nTorvild\nNationality: Norwegian\n" : []

将来用新字段扩展Entry数据会很好,但这样做需要在entryParser函数中添加更多重复的代码。有没有办法让这个函数接受解析器列表?

我从这开始:

attributeParsers =
  map attributeParser ["Date of Birth", "Nationality", "Parentage"]

permuteParams =
  map (\p -> (Nothing, liftM Just (try p))) attributeParsers

但是无法以正确的方式将permuteParams<|?>运算符一起折叠(我想它需要比(,,)元组构造函数更聪明的东西)。

2 个答案:

答案 0 :(得分:2)

(<|?>)与折叠效果不佳,因为您作为第一个参数传递的StreamPermParser的类型与StreamPermParser结果的类型相同。对于一个更简单但类似的问题,如果您尝试在应用样式中使用(,,) (<$>)(<*>)(例如(,,) <$> foo <*> bar <*> baz),则会遇到类似问题。

如果你想减少一些重复,我平淡无奇的建议是使用当地的定义:

entryParser :: Parser Entry
entryParser = do
  (f, l) <- nameParser
  (d, n, p) <- permute ((,,)
    <$?> optField "Date of Birth"
    <|?> optField "Nationality"
    <|?> optField "Parentage"
    )
  return $ Entry f l d n p
  where
  optField fieldName = (Nothing, liftM Just (try $ attributeParser fieldName))

答案 1 :(得分:2)

作为第一步,您可以抽象为每个组件做的事情:

attr txt = (Nothing, liftM Just (try $ attributeParser txt))

有了这个,你可以去:

entryParser :: Parser Entry
entryParser = do
  (f, l) <- nameParser
  (d, n, p) <- permute ((,,)
    <$?> attr "Date of Birth"
    <|?> attr "Nationality"
    <|?> attr "Parentage"
    )
  return $ Entry f l d n p

然后,如果需要,您可以组合中缀组合器和attr来电:

f .$ x = f <$?> attr x
f .| x = f <|?> attr x

infixl 2 .$
infixl 2 .|

这会给你:

entryParser :: Parser Entry
entryParser = do
  (f, l) <- nameParser
  (d, n, p) <- permute ((,,)
    .$ "Date of Birth"
    .| "Nationality"
    .| "Parentage"
    )
  return $ Entry f l d n p

然后你可以通过摆脱中间三重来进一步简化。您所做的只是构建它,然后将其组件应用到Entry f l,这样您就可以直接将排列解析器的结果应用于Entry f l

entryParser :: Parser Entry
entryParser = do
  (f, l) <- nameParser
  permute (Entry f l
    .$ "Date of Birth"
    .| "Nationality"
    .| "Parentage"
    )

我认为这很紧凑。如果你真的想要某种折叠,你要么必须引入一个中间列表并在列表中收集排列结果。但是,只有所有可置换属性属于同一类型(它们当前属于)时才会起作用,并且不太好,因为您将对此列表中的元素数量进行假设。或者你将不得不使用异构列表/某种类型的魔术,这将导致类型更复杂,我认为,这里不值得。

相关问题