Как работает EitherT?

голоса
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и StateTфункцию. Я нахожусь под впечатлением , что теперь я должен быть в состоянии написать что - то вроде этого:

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

Более важным является захват Eitherвозвращаемых значений , которые должны быть возможно с hoistEitherиз errorsпакета:

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

Я прочитал реальный мир Haskell главы о монаде трансформаторах и возился с lift. Но я не могу ничего typecheck.

Задан 20/01/2013 в 21:10
источник пользователем
На других языках...                            


2 ответов

голоса
9

Причина вы не можете просто использовать leftи hoistEitherфункцию непосредственно в том , что в отличие StateTи ReaderTот mtlупаковки, eitherупаковка не обеспечивает аналогичную класс типов MonadReaderили MonadState.

Вышеперечисленные классы типов заботиться о подъеме в стеке монады прозрачно, но EitherTвы должны сделать подъемный самостоятельно (или написать MonadEitherкласс типов , похожие на MonadReaderдр).

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

Прежде всего , необходимо применить Stuffобертку, а затем liftчерез ReaderTтрансформатор , а затем liftснова через StateTтрансформатор.

Вы, вероятно, хотите, чтобы написать функции полезности для себя такие, как

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.Errorс mtl, если определить Errorэкземпляр для Text.

instance Error T.Text where
  strMsg = T.pack

Теперь вы можете изменить определение Stuffреализации leftи 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типа проверок без какого - либо ручного подъема.

Вы также можете написать общие реализации для leftи hoistEitherкоторые работают для любого экземпляра MonadError( с использованием eitherот Data.Either):

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

hoistEither :: MonadError e m => Either e a -> m a
hoistEither = either throwError return
Ответил 20/01/2013 в 21:53
источник пользователем

голоса
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
Ответил 22/01/2013 в 09:34
источник пользователем

Cookies help us deliver our services. By using our services, you agree to our use of cookies. Learn more