3

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。問題は、eitherReadera を期待している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 計算を実行する必要があるため、エラー メッセージが である必要があるように思われるため、私が求めていることが可能かどうかはわかりません。したがって、少なくともeitherReaderaString -> IO (Either String a)または aを取得する必要があるようですString -> Either (IO String) (IO Handle)。それらの基本的な理解から、ここでモナド変換子を使用して ReadM をラップできるように思えます (またはその逆?)。しかし、それは私の理解が進むよりも少し深いものであり、どのように先行するかについて途方に暮れています.

optparse-applicative でhandleingを達成する方法はありますか?IOExceptionReadM

4

1 に答える 1

4

あなたのアプローチはやや間違っていると思います。

あなたが言った:"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他のものは、の仕事ではありません (そしてすべきではありません) 。

したがって、この datatype のパーサーを構築するだけ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.
于 2017-07-03T03:39:10.943 に答える