3

短縮版

hereと同じ質問ですMonadResourceが、明示的ではなく一般的なインスタンス内にありますResourceT m

ロングバージョン

catch次のような関数をどのように定義しますか。

import Control.Exception            (Exception, IOException)
import Control.Monad.Trans.Resource (MonadResource, runResourceT)

catch :: (MonadResource m, Exception e) -> m () -> (e -> m ()) -> m ()
catch = undefined

-- 'a' and 'b' are functions from an external library,
-- so I can't actually change their implementation
a, b :: MonadResource m => m ()
a = -- Something that might throw IO exceptions
b = -- Something that might throw IO exceptions

main :: IO ()
main = runResourceT $ do
    a `catch` \(e :: IOException) -> -- Exception handling
    b `catch` \(e :: IOException) -> -- Exception handling

私が遭遇する問題は次のとおりです。

  • ではControl.ExceptioncatchIOの s でのみ機能します。
  • ではControl.Exception.Liftedcatchのインスタンスが必要ですが、残念ながらそうMonadBaseControlではありません(なぜだろうか)。MonadResource
  • MonadResourceMonadThrow'catch' に相当するものなしで関数を定義することを意味しますmonadThrow(そして、なぜだろうか)。

IO例外を処理する唯一の方法はレイヤーを終了することのように見えますが、ResourceTこれは私を悩ませています: モナドトランスフォーマースタックを経由せずにローカルで例外を処理できるようにしたい.

参考までに、私の実際のコードでは、abは実際には のhttp関数ですNetwork.HTTP.Conduit

あなたの洞察に感謝します。

問題のある最小限のコード

ghc --make example.hsライブラリがインストールされた状態でコンパイル可能http-conduit:

{-# LANGUAGE FlexibleContexts, ScopedTypeVariables #-}
import Control.Exception.Lifted     (IOException, catch)
import Control.Monad.Base           (liftBase)
import Control.Monad.Error          (MonadError(..), runErrorT)
import Control.Monad.Trans.Control  (MonadBaseControl)
import Control.Monad.Trans.Resource (MonadResource, runResourceT)

import Data.Conduit
import Data.Conduit.List            (consume)
import Data.Conduit.Text            (decode, utf8)
import Data.Text                    (Text)

import Network.HTTP.Client
import Network.HTTP.Conduit         (http)

main :: IO ()
main = do
    result <- runErrorT $ runResourceT f
    putStrLn $ "OK: " ++ show result

f :: (MonadBaseControl IO m, MonadResource m, MonadError String m) => m [Text]
f = do
    req      <- liftBase $ parseUrl "http://uri-that-does-not-exist.abc"
    manager  <- liftBase $ newManager defaultManagerSettings
    response <- (http req manager `catch` \(e :: IOException) -> throwError $ show e)
    response $$+- decode utf8 =$ consume

実行すると、このプログラムはエラーで終了し、次の出力が表示されます。

InternalIOException getAddrInfo: does not exist (Name or service not known)
4

4 に答える 4

1

必要なタイプ、

a, b :: MonadResource m, MonadBaseControl IO m => m ()

あなたが現在持っているタイプの特殊なケースです

a, b :: MonadResource m => m ()

唯一の違いは、追加のクラス制約です。コード内の型シグネチャを、デフォルトよりも一般的でないものにすることは自由です。aしたがって、とのシグネチャを変更するbだけで十分です。

于 2014-03-21T08:19:47.923 に答える
1

あなたの問題を正しく理解していれば、Lifted-baseを使用しても問題はありません。aandの型はbconstraint のみを使用しますMonadResource mが、他の追加のプロパティを持つモナドでそれらを使用できないという意味ではありません。たとえば、 内で計算を実行すると、 およびResourceTの制約が満たされ、から何でも使用できます。abControl.Exception.Lifted

-- ...
import Control.Exception.Lifted

-- 'a' and 'b' are functions from an external library,
-- so I can't actually change their implementation
a, b :: MonadResource m => m ()
a = undefined -- Something that might throw IO exceptions
b = undefined -- Something that might throw IO exceptions

main :: IO ()
main = runResourceT $ do
    a `catch` \(e :: IOException) -> undefined -- Exception handling
    b `catch` \(e :: IOException) -> undefined -- Exception handling
于 2014-03-22T07:55:31.573 に答える
0

の型シグネチャを変更して例外catchパッケージから含めるようにすると、簡単になります。MonadCatch

import Control.Monad.Trans.Resource (MonadResource, runResourceT)
import Control.Monad.Catch          (catch)

a, b :: MonadResource m => m ()
a = …
b = …

main :: IO ()
main = runResourceT $ do
    a `catch` \e -> …
    b `catch` \e -> …

これは、 norの変更を必要としないことに注意してください。ab

また、 duplode と Petr Pudlák の両方が、 のモナドを好きなだけ具体的catchにするのは自由だと指摘しています。したがって、これらのソリューションはどれも機能します。ab

于 2015-03-18T22:39:57.007 に答える