我编写了这样的排列解析示例:
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
与<|?>
运算符一起折叠(我想它需要比(,,)
元组构造函数更聪明的东西)。
答案 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"
)
我认为这很紧凑。如果你真的想要某种折叠,你要么必须引入一个中间列表并在列表中收集排列结果。但是,只有所有可置换属性属于同一类型(它们当前属于)时才会起作用,并且不太好,因为您将对此列表中的元素数量进行假设。或者你将不得不使用异构列表/某种类型的魔术,这将导致类型更复杂,我认为,这里不值得。