Haskell ReadP至少解析其中一个列表

Haskell ReadP parse at least one of the list

我尝试使用 ReadP 标准库解析命令(创建)。我的命令应以字符串 create 开头,然后至少包含一个 word/tag/due,并且可能包含一个选项。这是我的实际表达:

createExpr :: ReadP [Arg]
createExpr = do
  skipSpaces
  cmd <- SetCmd <$> cmdAliasExpr ["create", "add"]
  skipSpaces
  rest <-
    many1
    $   (AddWord <$> wordExpr)
    <|> (AddTag <$> addTagExpr)
    <|> (SetDue <$> dueExpr)
    <|> (AddOpt <$> optExpr)
  skipSpaces
  return $ cmd : rest

问题是,如果我只用一个选项调用 create,它会很好地解析。但它不应该,因为我预计至少有一个 word/tag/due。我该如何表达?


[编辑] 我找到了解决方案,感谢 @M. Aroosi

其实我用错了运算符。 <++本地的、独占的、偏左的选择,更符合我的需要。一旦一个表达式被匹配,它不应该检查其他的:

notAnOpt arg = case arg of
  AddOpt _ -> False
  _        -> True


createExpr :: ReadP [Arg]
createExpr = do
  skipSpaces
  cmd <- SetCmd <$> cmdAliasExpr ["create", "add"]
  skipSpaces
  rest <-
    many1
    $   (AddTag <$> addTagExpr)
    <++ (SetDue <$> dueExpr)
    <++ (AddOpt <$> optExpr)
    <++ (AddWord <$> wordExpr)
  skipSpaces
  guard $ isJust $ find notAnOpt rest
  return $ cmd : rest

一个简单的解决方案是使用 Control.Monad 中的 guard
假设一个类似 isOpt :: Arg -> Bool 的函数是

isOpt :: Arg -> Bool
isOpt (AddOpt _) = True
isOpt _          = False 

然后您对 createExpr 的定义更改为

createExpr :: ReadP [Arg]
createExpr = do
    skipSpaces
    cmd <- SetCmd <$> cmdAliasExpr ["create", "add"]
    skipSpaces
    rest <-
      many1
      $   (AddWord <$> wordExpr)
      <|> (AddTag <$> addTagExpr)
      <|> (SetDue <$> dueExpr)
      <|> (AddOpt <$> optExpr)
    guard $ at_least_one_non_optional rest
    skipSpaces
    return $ cmd : rest
  where at_least_one_non_optional = not . null . filter (not . isOpt)

guard 当其参数为 False 时,解析器基本上会失败,更一般地,当参数为 [=17] 时,它通过返回 empty 来处理任何 Alternative =].