Question Details

No question body available.

Tags

exception haskell monads

Answers (3)

March 30, 2025 Score: 8 Rep: 52,774 Quality: High Completeness: 60%

The design choice for System.Directory was probably based on the fact that, for example, System.IO already worked this way. The original design choice for System.IO might have had something to do with the fact that working within a nested monad like IO (Either e a) is tedious.

For example, consider a simple function based on the current design, like:

plainFiles :: FilePath -> IO [FilePath]
plainFiles dir = filterM (fmap isRegularFile . getFileStatus) = IO (Either IOError FileStatus)

Writing a straightforward implementation of plainFiles' :: FilePath -> IO (Either IOError [FilePath] is messy business.

An mtl-based solution might make sense, so functions would have signatures like:

listDirectory :: (MonadIO m, MonadError IOError m) => FilePath -> m [FilePath]

but not everyone wants to be forced to use mtl just because they want to list directory contents in their Haskell scriopt.

Anyway, it's easy enough to write a general purpose adapter for mtl.

liftEIO :: (MonadIO m, MonadError IOError m) => IO a -> m a
liftEIO act = liftIO (try act) >>= liftEither

which lets you adapt the functions you want to use:

listDirectory' :: (MonadIO m, MonadError IOError m) => FilePath -> m [FilePath]
listDirectory' = liftEIO . listDirectory

getFileStatus' :: (MonadIO m, MonadError IOError m) => FilePath -> m FileStatus getFileStatus' = liftEIO . getFileStatus

and write programs in the monad of your choice:

type M = ExceptT IOError IO

plainFiles' :: FilePath -> M [String] plainFiles' dir = filterM (fmap isRegularFile . getFileStatus') = FilePath -> m [FilePath] listDirectory' = liftEIO . listDirectory

getFileStatus' :: (MonadIO m, MonadError IOError m) => FilePath -> m FileStatus getFileStatus' = liftEIO . getFileStatus

type M = ExceptT IOError IO

runM :: M a -> IO a runM act = runExceptT act >>= liftEither

plainFiles' :: FilePath -> M [String] plainFiles' dir = filterM (fmap isRegularFile . getFileStatus') =
March 30, 2025 Score: 4 Rep: 156,135 Quality: Low Completeness: 30%

If you have a plan for recovering from this problem, by all means use try or one of the other exception-handling mechanisms. That is exactly what they're there for.

March 30, 2025 Score: 3 Rep: 357 Quality: Medium Completeness: 100%

No, you should not accept exceptions to be unrecoverable.

The hackage page for Control.Exception recommends handling exceptions with try except for asynchronous exceptions.

Here's a rule of thumb for deciding which catch-style function to use:

  • If you want to do some cleanup in the event that an exception is raised, use finally, bracket or onException.

  • To recover after an exception and do something else, the best choice is to use one of the try family.

  • ... unless you are recovering from an asynchronous exception, in which case use catch or catchJust.

"The three kinds of Haskell exceptions and how to use them" (Arnaud Spiwack, Tweag) has a different set of recommendations:

Haskell exceptions require a bit of care. But when used carefully they can be very effective.

  • The three kinds of exceptions correspond to how exceptions are thrown, but can’t be fully identified when they are caught. Therefore they require some discipline.

  • Use imprecise exceptions for programming errors. Throw with error, catch with bracket. Use HasCallStack for partial functions.

  • Use synchronous exceptions for exceptional behaviour. Throw specific error types with throwIO, catch specific error types with catch.

  • Asynchronous exceptions cancel threads. Throw with cancel, catch with bracket.

Since that article recommended catch, I gave that a try:

{-# LANGUAGE TypeApplications #-}

import System.Directory import Control.Exception

goodPath = "." badPath = "./nonexistent"

ls :: FilePath -> IO [FilePath] ls dir = catch @IOException (getDirectoryContents dir) nullDirectory -- @IOException: names a specific type of exception, requires TypeApplication

nullDirectory :: IOException -> IO [FilePath] nullDirectory e = do putStrLn "No Such Directory" return []