4

私は Haskell アプリケーションを構築しており、エラー処理メカニズムを構築する方法を理解しようとしています。実際のアプリケーションでは、Mongo を使ってさまざまな作業を行っています。ただし、このために、ファイルに対する基本的な IO 操作を使用して単純化します。

したがって、このテスト アプリケーションでは、ファイルを読み込んで、各値がスペースで区切られた適切なフィボナッチ数列が含まれていることを確認します。

1 1 2 3 5 8 13 21

さて、ファイルを読むとき、実際にはいくつものことが間違っている可能性があります.Haskellの単語の使用法で、これらの例外のすべてを呼び出します.

data FibException = FileUnreadable IOError
                  | FormatError String String
                  | InvalidValue Integer
                  | Unknown String

instance Error FibException where
    noMsg = Unknown "No error message"
    strMsg = Unknown

シーケンスを検証し、シーケンスが無効な場合にエラーをスローする純粋な関数を作成するのは簡単です (ただし、おそらくもっとうまくやれるはずです)。

verifySequence :: String -> (Integer, Integer) -> Either FibException ()
verifySequence "" (prev1, prev2) = return ()
verifySequence s (prev1, prev2) =
    let readInt = reads :: ReadS Integer
        res = readInt s in
    case res of
        [] -> throwError $ FormatError s
        (val, rest):[] -> case (prev1, prev2, val) of
            (0, 0, 1) -> verifySequence rest (0, 1)
            (p1, p2, val') -> (if p1 + p2 /= val'
                then throwError $ InvalidValue val'
                else verifySequence rest (p2, val))
            _ -> throwError $ InvalidValue val

その後、ファイルを読み取ってシーケンスを検証する関数が必要です。

type FibIOMonad = ErrorT FibException IO

verifyFibFile :: FilePath -> FibIOMonad ()
verifyFibFile path = do
    sequenceStr <- liftIO $ readFile path
    case (verifySequence sequenceStr (0, 0)) of
        Right res -> return res
        Left err -> throwError err

Left (FormatError "something")この関数は、ファイルが無効な形式である場合 ( を返す)、またはファイルの番号が順序どおりでない場合 ( )に、まさに私が望むことを行いますLeft (InvalidValue 15)。ただし、指定されたファイルが存在しない場合はエラーになります。

readFile が生成する可能性のある IO エラーをキャッチして、それらを FileUnreadable エラーに変換するにはどうすればよいですか?

副次的な質問として、これはそれを行うための最良の方法でもありますか? verifyFibFileの呼び出し元が2 つの異なる例外処理メカニズムをセットアップする必要がなく、代わりに 1 つの例外タイプだけをキャッチできるという利点があることがわかりました。

4

3 に答える 3

3

EitherT一般的には、errorsパッケージを 考慮する必要があります。http://hackage.haskell.org/packages/archive/errors/1.3.1/doc/html/Control-Error-Util.htmltryIOにはキャッチIOErrorするためのユーティリティがあり、エラー値をカスタムタイプにマップするためにEitherT使用できます。fmapLT

具体的には:

type FibIOMonad = EitherT FibException IO

verifyFibFile :: FilePath -> FibIOMonad ()
verifyFibFile path = do
    sequenceStr <- fmapLT FileUnreadable (tryIO $ readFile path)
    hoistEither $ verifySequence sequenceStr (0, 0)
于 2013-01-05T20:13:34.070 に答える
1

@Savanni D'Gerinel: あなたは正しい道を進んでいます。エラーをキャッチするコードをverifyFibFileから抽出してより一般的なものにし、ErrorT で直接動作するように少し変更してみましょう。

catchError' :: ErrorT e IO a -> (IOError -> ErrorT e IO a) -> ErrorT e IO a
catchError' m f =
    ErrorT $ catchError (runErrorT m) (fmap runErrorT f)

verifyFibFileは次のように記述できるようになりました。

verifyFibFile' :: FilePath -> FibIOMonad ()
verifyFibFile' path = do
    sequenceStr <- catchError' (liftIO $ readFile path) (throwError . FileUnReadable)
    ErrorT . return $ verifySequence sequenceStr' (0, 0) 

catchError'で行ったことに注目してください。アクションから ErrorT コンストラクターを削除しErrorT e IO a、エラー処理関数の戻り値からも削除しましたが、後で制御操作の結果を再度 ErrorT にラップすることでそれらを再構築できることを知っています。

これは一般的なパターンであり、ErrorT 以外のモナド変換子でも実行できることがわかりました。ただし、注意が必要な場合があります (たとえば、ReaderT でこれを行うにはどうすればよいでしょうか?)。幸いなことに、モナド制御パッケージは、多くの一般的なトランスフォーマーにこの機能をすでに提供しています。

モナド制御の型シグネチャは、最初は恐ろしく見えるかもしれません。1 つの関数だけを調べることから始めます: control。次のタイプがあります。

control :: MonadBaseControl b m => (RunInBase m b -> b (StM m a)) -> m a

bbe にすることで、より具体的にしましょうIO:

control :: MonadBaseControl IO m => (RunInBase m IO -> IO (StM m a)) -> m a

mはIOの上に構築されたモナド スタックです。あなたの場合、それはErrorT IOになります。

RunInBase m IOm atype の値を取り、 typeの値を返す魔法の関数の型エイリアスです。これは、IO *something*IO内のモナド スタック全体の状態をエンコードし、後で値を再構築できる複雑な魔法ですm aだまされた」 IO 値のみを受け入れる制御操作。controlはその機能を提供し、再構築も処理します。

これをあなたの問題に適用すると、verifyFibFileを次のようにもう一度書き換えます。

import Control.Monad.Trans.Control (control)
import Control.Exception (catch)


verifyFibFile'' :: FilePath -> FibIOMonad ()
verifyFibFile'' path = do
    sequenceStr <- control $ \run -> catch (run . liftIO $ readFile path) 
                                           (run . throwError . FileUnreadable)     
    ErrorT . return $ verifySequence sequenceStr' (0, 0) 

これは、 の適切なインスタンスがMonadBaseControl b m存在する場合にのみ機能することに注意してください。

これはモナド制御の素晴らしい紹介です。

于 2013-01-05T16:32:59.737 に答える
0

だから、ここに私が開発した答えがあります。readFileそれは、適切なステートメントに包まれてcatchErrorから持ち上げられることを中心にしています。

verifyFibFile :: FilePath -> FibIOMonad ()
verifyFibFile path = do
    contents <- liftIO $ catchError (readFile path >>= return . Right) (return . Left . FileUnreadable)
    case contents of
        Right sequenceStr' -> case (verifySequence sequenceStr' (0, 0)) of
            Right res -> return res
            Left err -> throwError err
        Left err -> throwError err

したがって、verifyFibFileこのソリューションではもう少しネストされます。

readFile pathIO String明らかにタイプがあります。このコンテキストでは、の型は次のようにcatchErrorなります。

catchError :: IO String -> (IOError -> IO String) -> IO String

したがって、私の戦略は、エラーをキャッチしてどちらかの左側に変換し、成功した値を右側に変換して、データ型を次のように変更することでした。

catchError :: IO (Either FibException String) -> (IOError -> IO (Either FibException String)) -> IO (Either FibException String)

これを行うには、最初のパラメーターで結果を Right にラップするだけです。return . Right成功しない限り、コードのブランチを実際に実行することはないと思いますreadFile path。キャッチする他のパラメーターでは、 で開始し、IOErrorでラップしてLeftから、コンテキストに戻しIOます。その後、結果がどうであれ、IO 値をFibIOMonadコンテキストに持ち上げます。

コードがさらにネストされるという事実に悩まされています。私にはLeft値があり、それらのLeft値はすべてスローされます。私は基本的にコンテキストにいます.クラスのEither利点Eitherの実装の1つは、値がバインディング操作を介して単純に渡され、そのコンテキストでそれ以上コードが実行されないことであると考えていました. これについての説明、またはこの関数からネストを削除する方法を確認したいと思います。MonadLeft

多分それはできません。ただし、呼び出し元はverifyFibFile繰り返し呼び出すことができ、実行は基本的に最初に停止するとverifyFibFileエラーが返されるようです。これは機能します:

runTest = do
    res <- verifyFibFile "goodfib.txt"
    liftIO $ putStrLn "goodfib.txt"
    --liftIO $ printResult "goodfib.txt" res

    res <- verifyFibFile "invalidValue.txt"
    liftIO $ putStrLn "invalidValue.txt"

    res <- verifyFibFile "formatError.txt"
    liftIO $ putStrLn "formatError.txt"

Main> runErrorT $ runTest
goodfib.txt
Left (InvalidValue 17)

私が作成したファイルを考えると、invalidValue.txt と formatError.txt の両方でエラーが発生しますが、この関数は返さLeft (InvalidValue ...)れます。

それは大丈夫ですが、私は自分のソリューションで何かを逃したような気がします. そして、これを MongoDB アクセスをより堅牢にするものに変換できるかどうかはわかりません。

于 2013-01-05T03:45:41.523 に答える