In many cases, these deeply nested indents are the result of deeply nested error checking. If so, you should take a peek at MaybeT and his older brother ExceptT . They offer a clean way to separate the code "what do we do when something went wrong" from the code "what do we do if everything goes right." In your example, I can write:
data CustomError = IfCheckFailed | MaybeCheckFailed main = handleErrors <=< runExceptT $ do inFH <- liftIO $ openFile ... outFH <- liftIO $ openFile ... forM myList $ \item -> do when (...) (throwError IfCheckFailed) ... x <- liftMaybe MaybeCheckFailed ... ... liftMaybe :: MonadError em => e -> Maybe a -> ma liftMaybe err = maybe (throwError err) return handleErrors :: Either CustomError a -> IO a handleErrors (Left err) = case err of IfCheckFailed -> ... MaybeCheckFailed -> ... handleErrors (Right success) = return success
Note that we are still increasing the indentation in the forM loop; but other checks are performed "in-line" in main and processed at the same level of indentation in handleErrors .
source share