在optparse-applicative ReadM中处理openFile中的异常

时间:2017-07-02 17:43:04

标签: haskell monads optparse-applicative

使用optparse-applicative,我想要一个可选参数,该参数应该是文件的路径,或者未指定时stdin。这里显而易见的选择是使这个参数类型为IO Handle,并且在使用openFile时传递参数。这就是我目前所拥有的:

module Main where

import Data.Semigroup ((<>))
import Options.Applicative
import System.IO

data Args = Args { input :: IO Handle }

parseArgs = Args <$> argument parseReadHandle (value defaultHandle)
  where defaultHandle = return stdin :: IO Handle

parseReadHandle :: ReadM (IO Handle)
parseReadHandle = eitherReader $ \path -> Right $ openFile path ReadMode

getArgs :: IO Args
getArgs = execParser $ info (parseArgs <**> helper) fullDesc

main :: IO ()
main = run =<< getArgs

run :: Args -> IO ()
run (Args input) = putStrLn =<< hGetContents =<< input

问题是,我们没有handle来自openFile的异常,而是依赖于未处理异常的默认行为(打印错误和退出)。这看起来很难吃。

我认为更合适的方法是使用Left的错误消息返回openFile。问题是,eitherReader期望String -> Either String a,因此我们无法执行以下操作:

{-# LANGUAGE ScopedTypeVariables #-}
import Control.Exception

parseReadHandle :: ReadM (IO Handle)
parseReadHandle = eitherReader tryOpenFile

tryOpenFile :: IO (Either String (IO Handle)) -> FilePath
tryOpenFile path = do
  handle (\(e :: IOException) -> return $ Left $ show e) $ do
    return $ Right $ openFile path ReadMode

当然,您可以从tryOpenFile的类型中看出这不会出现问题。我不确定我要求的是否可能,因为似乎错误消息必须是IO String,因为要获得错误,必须执行IO计算。因此,至少看起来您需要eitherReader来获取String -> IO (Either String a)String -> Either (IO String) (IO Handle)。根据我对它们的基本理解,听起来像monad变换器可以用来包装ReadM(或者反过来?)。但这比我的理解要深刻得多,而且我对如何先行而感到茫然。

有没有办法在optparse-applicative handle中完成IOException ReadM

1 个答案:

答案 0 :(得分:2)

我相信你的方法有点误导。

你说:"I'd like to have an optional argument, which should be a path to a file..."

好的,那么Maybe FilePath的内容怎么样?听起来这可能是你想要的。或等效的ADT:

data Path = StandardInput | Path FilePath

当你说"The obvious choice here is to make this argument type IO Handle and when an argument is passed in use openFile"时  你领先于自己。

从命令行解析应该是将要解析的输入转换为适合将来在程序中使用的数据。不要担心在这个阶段打开文件,或者在文件不存在时处理异常,或者你将使用这些数据的任何其他方式...只是担心这个问题,我的程序的用户是否给了我一个文件路径?也就是说,我有什么数据?其他的东西不是(也不应该)optparse-applicative的工作。

所以只需为此数据类型Path构建解析器。它可以由每个构造函数的解析器组成。 E.g:

stdInputParser :: Parser Path
stdInputParser = ...

pathSuppliedParser :: Parser Path
pathSuppliedParser = ...

pathParser :: Parser Path
pathParser = pathSuppliedParser <|> stdInputParser

无论如何,运行execParser后,您将获得Path数据类型。因此,您将其作为参数传递给run函数:

run :: Path -> IO ()
run StandardInput = ... use stdin here
run (Path filePath) = ... use openFile here, catch and handle exceptions if the file doesn't exist, etc.