user6023611
user6023611

Reputation:

using ErrorT transformer monad in do section

I would like to understand how to deal with stack of transformators in do section. It is also about error handling.

Namely, I will show you my problem in my case.
I have some funcion:

foo :: Int -> String -> IO (Either String (Value, Integer))

Body of foo is not important here.

ef :: evalExpr :: Int -> String ->  IO (Either String (Value, Integer))

Now,

f :: Integer -> String -> StateT Integer (ReaderT Integer (ErrorT String IO)) ()
f i s = do
  vl <- lift $ lift $ lift $ ef i s
  ....
  return ()

ef can call throwError. Also it can return (Value, String).
I would like to be able (....) do something with Right value (so (Value, String)). However, I can't do it. It is my permanent problem.

Idea is following:
When ef called throwError then foo should propagate the same error (so Left String). Neverhteless, when ef return Right Value (so (Value, String)) I would like to use it and on this base make decision what should return foo - throwError or (Value, ())

I know about fmap and <*>. Can you help me, please ?

Upvotes: 1

Views: 32

Answers (1)

Bartek Banachewicz
Bartek Banachewicz

Reputation: 39370

The function:

ef :: Int -> String ->  IO (Either String (Value, Integer))

Can only throwError an IOException:

instance MonadError IOException IO where
    throwError = ioError
    catchError = catch

Otherwise, it's just an IO function returning Either String (Value, Integer).

Given that function, to act on its value you need to execute it in an IO-capable context, and then simply use the resulting value:

callEf :: IO ()
callEf = do
    result <- ef ...
    case result of
        Left s ->         -- do something with the String
        Right (v, i) ->   -- do something with the tuple

    return ()
    -- ^ you can also obviously produce the value above,
    -- if you want something more complicated than ()

This doesn't change if you're in a monad stack. In your function f, calling lift three times give you an IO context. After that, you can do whatever you want with the resulting value, including any operation from the stack, like throwError (after two lifts) or put (with no lifts).

Alternatively, refer to my answer to your previous question to get rid of the lifts.

If you want the error propagation to happen automatically from Either to ErrorT, it would be reasonably simple, just like above:

eitherToError :: Either e v -> ErrorT e m v
eitherToError f = case f of
    Left err -> throwError err
    Right val -> return val

Which you can easily search for in Hayoo, resulting in a very similar implementation in some library.


All that being said, it seems that ErrorT is deprecated in favor of Control.Monad.Trans.Except.

Upvotes: 2

Related Questions