【问题标题】:Optparse-applicative: consecutive parsing (ReadM)Optparse-applicative:连续解析(ReadM)
【发布时间】:2020-07-30 16:33:00
【问题描述】:

我有一个基本命令add,它接受两种参数:一个词或一个标签。标签只是一个以+ 开头的单词。一个词只是一个String。它可以包含至少一个参数(我为此使用some)。

data Arg = Add AddOpts

data AddOpts = AddOpts
  { desc :: String,
    tags :: [String]
  }
  deriving (Show)

addCommand :: Mod CommandFields Arg
addCommand = command "add" (info parser infoMod)
  where
    infoMod = progDesc "Add a new task"
    parser = Add <$> parseDescAndTags <$> partition isTag <$> some (argument str (metavar "DESC"))
    parseDescAndTags (_, []) = FAIL HERE
    parseDescAndTags (tags, desc) = AddOpts (unwords desc) (map tail tags)

我想添加另一条规则:add 命令应该至少接收一个单词(但有 0 个或多个标签)。为此,我需要在第一次解析单词列表后进行检查。如果它是空的,我想失败,好像add 命令没有收到任何参数,但我不知道该怎么做。

【问题讨论】:

    标签: haskell monads applicative optparse-applicative


    【解决方案1】:

    parseDescAndTags目前是一个纯函数,所以没有办法导致解析失败。只是为了解决这个问题,我还应该注意在这段代码中:

    Add <$> parseDescAndTags <$> partition isTag <$> some (argument str (metavar "DESC"))
    

    运算符&lt;$&gt; 被声明为infixl 4,所以它是左结合的,因此你的表达式等价于:

    ((Add <$> parseDescAndTags) <$> partition isTag) <$> some (argument str (metavar "DESC"))
    

    你恰好在“函数阅读器”函子中使用了&lt;$&gt;(-&gt;) a,相当于组合(.)

    Add . parseDescAndTags . partition isTag <$> some (argument str (metavar "DESC"))
    

    如果要使用ReadM,则需要使用eitherReader等函数来构造ReadM动作。但问题是您需要将它用作argument 而不是str 阅读器的第一个参数,这是错误的地方,因为some外部并且您希望根据 whole 选项的累积结果解析失败。

    不幸的是,这种上下文相关的解析不是optparse-applicative 所设计的;它没有为解析器提供Monad 实例。

    目前,您的解析器允许标签和描述交错,像这样(假设isTag = (== ".") . take 1 用于说明):

    add some .tag1 description .tag2 text
    

    生成"some description text" 作为描述,[".tag1", ".tag2"] 作为标签。这是你想要的,还是你可以使用更简单的格式,比如最后要求所有标签?

    add some description text .tag1 .tag2
    

    如果是这样,结果很简单:用some 解析至少一个非标签,然后用many 解析任意数量的标签:

    addCommand :: Mod CommandFields Arg
    addCommand = command "add" (info parser infoMod)
      where
        infoMod = progDesc "Add a new task"
        parser = Add <$> addOpts
        addOpts = AddOpts
          <$> (unwords <$> some (argument nonTag (metavar "DESC")))
          <*> many (argument tag (metavar "TAG"))
    
        nonTag = eitherReader
          $ \ str -> if isTag str
            then Left ("unexpected tag: '" <> str <> "'")
            else Right str
    
        tag = eitherReader
          $ \ str -> if isTag str
            then Right $ drop 1 str
            else Left ("not a tag: '" <> str <> "'")
    

    作为替代方案,您可以使用optparse-applicative 解析 命令行选项,但在运行解析器后对您的选项记录执行任何更复杂的验证。然后如果你想手动打印帮助文本,你可以使用:

    printHelp :: ParserPrefs -> ParserInfo a -> IO a
    printHelp parserPrefs parserInfo = @987654322@ $ @987654323@
      $ @987654324@ parserPrefs parserInfo @987654325@ mempty

    【讨论】:

    • 感谢您的简化。我对 Haskell 还是新手,这些事情还不是很容易想到的。我尝试做的是将单词和标签混合在一起,这就是我选择parseDescAndTags 的原因。我喜欢你的第二种方法。我仍然会搜索更多,看看是否找不到更好的选择。我想打印与 arg 完全丢失一样的输出。
    • 我正在考虑抛出一个错误,并显示你在 catch 中提到的帮助。它在 Haskell 中被认为是一种好的做法吗?
    • @soywod:如果您预期会发生错误,您应该更喜欢值级错误处理,例如MaybeEitherValidation。在这里,您可以解析为 Arg 的“未验证参数”newtype 包装器,然后使用函数 validate :: UnvalidatedArg -&gt; Either String Arg 来检查无法直接进入解析器的不变量。如果结果为Left message,则打印帮助和错误消息。异常(throwthrowIO)更适合unexpectedunrecoverable 错误;另外,你只能在IO中捕获异常,所以你不能在普通的纯代码中使用它们。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-10-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多