8

私は一日の半分を、コードのエラーに対処する方法として、EitherTを使用する方法を理解するために費やしています。

私はこのようなトランススタックを定義しました。

-- Stuff Monad

data StuffConfig = StuffConfig {
  appId     :: T.Text,
  appSecret :: T.Text
}

data StuffState = StuffState {
  stateToken :: Maybe Token,
  stateTime  :: POSIXTime
}

newtype Stuff a = Stuff {
  runStuff :: (ReaderT StuffConfig (StateT StuffState (EitherT T.Text IO))) a
} deriving (Monad, Functor, Applicative, 
            MonadIO, 
            MonadReader StuffConfig,
            MonadState StuffState
            )



askStuff :: StuffConfig -> Stuff a -> IO (Either T.Text a)
askStuff config a = do
  t <- getPOSIXTime 
  runEitherT (evalStateT (runReaderT (runStuff a) config) (StuffState Nothing t))

ReaderTこれは、 and関数のみを使用する限り非常にうまく機能しStateTます。私は今、私はこのようなものを書くことができるはずだという印象を受けています:

faultyFunction :: String -> Stuff String
faultyFunction s = do
  when s == "left" $ left "breaking out"
  "right"

さらに重要なのは、パッケージからEither可能なはずの戻り値を取得することです。hoistEithererrors

faultyLookup :: Map -> String -> Stuff String
faultyLookup m k = do
  hoistEither $ lookup k m

私はモナド変換子に関する現実世界のハスケルの章を読み、をいじりましたlift。しかし、私はタイプチェックするものを何も得ることができません。

4

2 に答える 2

9

leftandhoistEither関数を直接使用できない理由は、パッケージStateTとは異なり、パッケージはまたはに類似した型クラスを提供しないためです。ReaderTmtleitherMonadReaderMonadState

前述の型クラスは、モナドスタックでのリフティングを透過的にEitherT処理しますが、の場合は、自分でリフティングを行う必要があります(または、 et alMonadEitherと同様の型クラスを記述しますMonadReader)。

faultyFunction :: String -> Stuff String
faultyFunction s = do
  when (s == "left") $ Stuff $ lift $ lift $ left "breaking out"
  return "right"

Stuff最初にラッパーを適用し、次にトランスフォーマーに適用liftし、次にトランスフォーマーに適用する必要があります。ReaderTliftStateT

おそらく、次のようなユーティリティ関数を自分で作成する必要があります。

stuffLeft :: T.Text -> Stuff a
stuffLeft = Stuff . lift . lift . left

次に、次のように簡単に使用できます。

faultyFunction :: String -> Stuff String
faultyFunction s = do
  when (s == "left") $ stuffLeft "breaking out"
  return "right"

または、のインスタンスを定義する場合は、Control.Monad.Errorfromを使用できます。mtlErrorText

instance Error T.Text where
  strMsg = T.pack

Stuffこれで、 implementleftの定義を次のhoistEitherように変更できます。

newtype Stuff a = Stuff {
  runStuff :: (ReaderT StuffConfig (StateT StuffState (ErrorT T.Text IO))) a
} deriving (Monad, Functor, Applicative,
            MonadIO,
            MonadReader StuffConfig,
            MonadState StuffState,
            MonadError T.Text
            )

left :: T.Text -> Stuff a
left = throwError

hoistEither :: Either T.Text a -> Stuff a
hoistEither = Stuff . lift . lift . ErrorT . return

faultyFunctionこれを使用すると、手動で持ち上げることなく、元のタイプチェックを実行できます。

( fromを使用して)の任意のインスタンスに対して機能する一般的な実装を作成することもできleftます。hoistEitherMonadErroreitherData.Either

left :: MonadError e m => e -> m a
left = throwError

hoistEither :: MonadError e m => Either e a -> m a
hoistEither = either throwError return
于 2013-01-20T20:53:44.887 に答える
2

shangの答えに追加するだけです:MonadError基本的にはに対応する型クラスEitherTです。そのインスタンスを追加できますEitherT(何らかの理由でライブラリにコメントアウトされていeitherます):

import Control.Monad.Trans.Either
  hiding (left, right, hoistEither)

instance Monad m => MonadError e (EitherT e m) where
  throwError = EitherT . return . Left
  EitherT m `catchError` h = EitherT $ m >>= \a -> case a of
    Left  l -> runEitherT (h l)
    Right r -> return (Right r)

次に、一般化された独自のメソッドを定義しますMonadError

left :: MonadError e m => e -> m a
left = throwError
{-# INLINE left #-}

right :: MonadError e m => a -> m a
right = return
{-# INLINE right #-}

hoistEither :: MonadError e m => Either e a -> m a
hoistEither (Left a)  = throwError a
hoistEither (Right e) = return e
{-# INLINE hoistEither #-}

今、あなたは次のようなことをすることができます:

import qualified Data.Map as Map

newtype Stuff a = Stuff {
  runStuff :: (ReaderT Int (StateT Char (EitherT T.Text IO))) a
} deriving (Monad, Functor,
            MonadReader Int,
            MonadError T.Text, -- <--- MonadError instance
            MonadState Char
            )


faultyLookup :: (Ord k) => Map.Map k a -> k -> Stuff a
faultyLookup m k =
  maybe (left $ T.pack "Lookup error") right $ Map.lookup k m

またはそれを一般化する

faultyLookup :: (MonadError T.Text m, Ord k) => Map.Map k a -> k -> m a
faultyLookup m k =
  maybe (left $ T.pack "Lookup error") right $ Map.lookup k m
于 2013-01-22T08:34:50.193 に答える