Rerun computation after throwing error


I would like to combine the Error Monad and the IO Monad in Haskell. Here is an excerpt from my code:

type AskMonad = ErrorT String IO

askSomething :: AskMonad Direction
askSomething = do
  liftIO $ putStrLn "Choose an element from the list [1,2,3,4]"
  input <- liftIO getLine
  let entry = read input :: Int
  if entry `elem` [1,2,3,4]
  then return $ toEnum entry -- from Int to Direction
  else throwError "Invalid input!"

selectMove :: [Player] -> Board -> IO (Direction, Position)
selectMove players board = do
  direction <- runErrorT (askSomething) `catchError` --What to put here???
  -- Other IO stuff...

I would like to achieve the following behavior:

  • if askSomething succeeds, get the Direction.
  • if askSomething fails, show the error and run askSomething again.

However, I don't know what to put as the second argument of catchError in order to get this behavior. I think it should be of the following type signature: Either String Direction -> AskMonad Direction, but how can I access the computation that needs to be rerun (askSomething in this case).

Show source
| haskell   | monads   | monad-transformers   2017-01-03 17:01 1 Answers

Answers ( 1 )

  1. 2017-01-03 21:01

    The code as you have it now seems like it would be trying to catch exceptions not represented in the ErrorT String result. My thought is that you are actually looking to retry if runErrorT results in a Left value (an "expected" exception). In that case, something like this retryForever may work:

    retryForever :: AskMonad a -> IO a
    retryForever action =
      runErrorT action >>= either (\ex -> putStrLn ex >> retryForever action) return

    which could be used like:

    direction <- retryForever askSomething

    P.S. I believe ErrorT was deprecated in favor of ExceptT, and that ExceptT is only more general so the transition should be trivial if you decide to switch.

◀ Go back