2017-07-02 32 views
1

使用optparse-applicative,我想要一個可選參數,該參數應該是文件的路徑,或者未指定時,stdin。這裏明顯的選擇是使這個參數類型爲IO Handle,並且在使用openFile時傳遞參數。這是我目前有:在optparse-applicative中處理來自openFile的異常ReadM

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 

這樣做的問題是,我們沒有正確地從openFilehandle例外,而是依賴默認行爲未處理的異常(打印錯誤並退出)。這似乎很糟糕。

我認爲更正確的方法將返回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 ReadM中完成handleIOException

+0

代替'數據參數數量= {參數數量輸入:: IO手柄}'也許你需要'數據參數數量=參數數量{輸入::也許(IO處理)}'? –

+1

最簡單的可能的解決方案是用'Maybe FilePath'替換'IO Handle'並在程序參數解析後從文件中讀取。你的解析器變成'參數<$>參數(只是<$> str)(value Nothing)',你可以自由處理文件的存在/不存在。 'main'。反正這個邏輯不是解析器的一部分,解析器的類型反映了這一點。你總是可以使'input'成爲一個參數(即'data Args a = Args {input :: a}')並且有一個函數'Args FilePath - > IO(Args Handle)''。 – user2407038

回答

1

我相信你的方法有些誤導。

你說:"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. 
+0

我願意承認,我正在尋找的東西可能牽強。然而,我會爭辯說,但是僅僅爲了與monads混合使用而進行的學術練習,你是在說'eitherReader'的類型使得不可能做任何其他類型的單子計算? –

+0

我的評論是關於你應該做什麼,而不是你可以做什麼。您應該使用驗證庫來驗證您的數據,然後將驗證的數據傳遞給執行一次計算的函數。不要試圖在應用驗證器中不必要地強制一次性計算。請記住,圖書館是基於應用計算 - 而不是一次性計算。然而你想要的行爲基本上是一元的(即在某些值的情況下執行某些計算,在其他值的情況下執行其他計算)。這確實超出了optparse-applicative的範圍。 – liminalisht