The reason you can't just use the left and hoistEither functions is because, unlike StateT and ReaderT from mtl , either package does not provide a class similar to MonadReader or MonadState .
The above class classes will take care of removing the monode stack transparently, but for EitherT you need to do the upgrade itself (or write a MonadEither class, similar to MonadReader , etc.).
faultyFunction :: String -> Stuff String faultyFunction s = do when (s == "left") $ Stuff $ lift $ lift $ left "breaking out" return "right"
First you need to apply the Stuff wrapper, then lift over the ReaderT transformer, and then lift again over the StateT transformer.
You might want to write utility functions for yourself, for example
stuffLeft :: T.Text -> Stuff a stuffLeft = Stuff . lift . lift . left
Then you can simply use it as follows:
faultyFunction :: String -> Stuff String faultyFunction s = do when (s == "left") $ stuffLeft "breaking out" return "right"
Alternatively, you can use Control.Monad.Error from mtl if you define an Error instance for Text .
instance Error T.Text where strMsg = T.pack
Now you can change the definition of Stuff implement left and hoistEither as follows:
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
With this, your original faultyFunction checks without manual lifting.
You can also write down common implementations for left and hoistEither that work for any MonadError instance (using either from Data.Either ):
left :: MonadError em => e -> ma left = throwError hoistEither :: MonadError em => Either ea -> ma hoistEither = either throwError return