5

我刚刚发现我精心设计的解析器无法解析我扔给它的任何字符串:

roi :: Parser (Maybe ROI)
roi = optional $ option (ROI <$> auto <*> auto <*> auto <*> auto)
               $ long "roi" <> metavar "ROI" <> help "Only process selected region of interest"

在哪里ROI = ROI Int Int Int Int

如果这很重要,则它嵌套在更高的解析器中

options :: Parser Opts
options = Opts <$> input <*> output <*> roi <*> startT <*> endT  

哪里Opts是合适的 ADT。

现在我假设roi解析器将解析表达式,--roi 1 2 3 4但它失败Invalid argument '128'并给我使用消息。

--roi 1而是解析但返回Just (ROI 1 1 1 1)

有没有办法使这项工作?

4

2 回答 2

7

我不认为选项应该使用多个参数。至少我不确定您将如何实施。我建议简单地放弃这个想法,并将您的 ROI 选项放入一个参数中,使用类似--roi 1,2,3,4.

您只需要为此实现一个自定义阅读器,这是一个如何做到这一点的示例:

module Main where

import Options.Applicative

data ROI = ROI Int Int Int Int
  deriving Show

-- didn't remember what this function was called, don't use this
splitOn :: Eq a => a -> [a] -> [[a]]
splitOn sep (x:xs) | sep==x     = [] : splitOn sep xs
                   | otherwise = let (xs':xss) = splitOn sep xs in (x:xs'):xss
splitOn _ [] = [[]]

roiReader :: ReadM ROI
roiReader = do
  o <- str
  -- no error checking, don't actually do this
  let [a,b,c,d] = map read $ splitOn ',' o
  return $ ROI a b c d

roiParser :: Parser ROI
roiParser = option roiReader (long "roi")

main :: IO ()
main = execParser opts >>= print where
  opts = info (helper <*> roiParser) fullDesc
于 2016-12-16T14:19:33.873 回答
5

的类型option是:

option :: ReadM a -> Mod OptionFields a -> Parser a

ReadM,反过来,是“选项阅读器使用的'ReaderT String except'上的新类型”。由于在引擎盖下使用,当您将它与option您在此处所做的实例一起使用时...ReaderTApplicativeReadM

ROI <$> auto <*> auto <*> auto <*> auto

... 为四个auto解析器中的每一个提供相同且完整的输入字符串,因为这就是阅读器/函数应用程序实例的工作方式。

如果您希望将由空格分隔的值解析为单个ROI,则需要编写自定义解析器。这是一个不是特别整洁的尝试,围绕eitherReader. 请注意,这将要求值在引号 ( --roi "1 2 3 4") 内,以便将它们作为单个字符串接收。Cubic 的回答提出了一种替代方法,它使用逗号分隔的值代替 ( --roi 1,2,3,4)。

import Text.Read (readEither)

-- etc.

roi :: Parser (Maybe ROI)
roi = optional
    $ option (eitherReader $ \inp -> case traverse readEither (words inp) of
        Right [x, y, z, w] -> Right (ROI x y z w)
        Right _ -> Left "ROI requires exactly 4 values"
        Left _ -> Left "ROI requires integer values")
    $ long "roi" <> metavar "ROI" <> help "Only process selected region of interest"

成功和失败模式:

GHCi> execParserPure defaultPrefs (info roi mempty) ["--roi","1 2 3 4"]
Success (Just (ROI 1 2 3 4))
GHCi> execParserPure defaultPrefs (info roi mempty) ["--roi","1 2 3"]
Failure (ParserFailure (option --roi: ROI requires exactly 4 values

Usage: <program> [--roi ROI],ExitFailure 1,80))
GHCi> execParserPure defaultPrefs (info roi mempty) ["--roi","1 2 foo 4"]
Failure (ParserFailure (option --roi: ROI requires integer values

Usage: <program> [--roi ROI],ExitFailure 1,80))
于 2016-12-16T14:26:06.210 回答