使用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
?
答案 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.