使用 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
期望 aString -> 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
看出 this 不会进行类型检查。我不确定我所要求的是否可行,因为似乎错误消息必须是IO String
,因为要获得错误,必须执行 IO 计算。所以至少看起来你需要eitherReader
采取 aString -> IO (Either String a)
或 a String -> Either (IO String) (IO Handle)
。根据我对它们的基本理解,听起来可以在这里使用 monad 转换器来包装 ReadM(或相反?)。但这比我的理解要深一些,我不知道如何先行。
有没有办法在handle
optparse IOException
-applicative 中完成ReadM
?