在 optparse-applicative ReadM 中处理来自 openFile 的异常
handle exception from openFile in a optparse-applicative ReadM
使用 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
的异常,而是依赖于未处理异常的默认行为(打印错误并退出)。这看起来很恶心。
我认为更正确的方法是 return 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
的类型可以看出,这个不会进行typecheck。我不确定我要求的是否可行,因为错误消息似乎必须是 IO String
,因为要获得错误,必须执行 IO 计算。所以至少看起来你需要 eitherReader
才能获得 String -> IO (Either String a)
或 String -> Either (IO String) (IO Handle)
。根据我对它们的基本理解,听起来这里可以使用 monad 转换器来包装 ReadM(或者反过来?)。但这比我的理解要深一些,我不知道如何先行。
有没有办法在 optparse-applicative ReadM
中完成 handle
ing 一个 IOException
?
我认为您的方法有些误入歧途。
你说:"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
。它可能由每个构造函数的解析器组成。例如:
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.
使用 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
的异常,而是依赖于未处理异常的默认行为(打印错误并退出)。这看起来很恶心。
我认为更正确的方法是 return 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
的类型可以看出,这个不会进行typecheck。我不确定我要求的是否可行,因为错误消息似乎必须是 IO String
,因为要获得错误,必须执行 IO 计算。所以至少看起来你需要 eitherReader
才能获得 String -> IO (Either String a)
或 String -> Either (IO String) (IO Handle)
。根据我对它们的基本理解,听起来这里可以使用 monad 转换器来包装 ReadM(或者反过来?)。但这比我的理解要深一些,我不知道如何先行。
有没有办法在 optparse-applicative ReadM
中完成 handle
ing 一个 IOException
?
我认为您的方法有些误入歧途。
你说:"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
。它可能由每个构造函数的解析器组成。例如:
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.