views:

106

answers:

3

I'd like to know the "Haskell way" to catch and handle exceptions. As shown below, I understand the basic syntax, but I'm not sure how to deal with the type system in this situation.

The below code attempts to return the value of the requested environment variable. Obviously if that variable isn't there I want to catch the exception and return Nothing.

getEnvVar x = do {
    var <- getEnv x;
    Just var;
} `catch` \ex -> do {
    Nothing
}

Here is the error:

Couldn't match expected type `IO a'
       against inferred type `Maybe String'
In the expression: Just var
In the first argument of `catch', namely
    `do { var <- getEnv x;
          Just var }'
In the expression:
      do { var <- getEnv x;
           Just var }
    `catch`
      \ ex -> do { Nothing }

I could return string values:

getEnvVar x = do {
    var <- getEnv x;
    return var;
} `catch` \ex -> do {
    ""
}

however, this doesn't feel like the Haskell way. What is the Haskell way?

Edit: Updated code to properly reflect description.

+6  A: 

You cannot strip away the IO and return Maybe String within a do-block. You need to return an IO (Maybe String).

getEnvVar x = do {
    var <- getEnv x;
    return (Just var);
} `catch` \ex -> do {
    return Nothing
}

Why not use

import qualified System.IO.Error as E
getEnvVar :: String -> IO (Either IOError String)
getEnvVar = E.try . getEnv

Instead of Nothing and Just var, you get Left error and Right var.

KennyTM
+1  A: 

You could also try

import System.Environment
getEnvVar :: String -> IO (Maybe String)
getEnvVar x = getEnvironment >>= return . lookup x

or a bit longer, but maybe easier to follow:

getEnvVar x = do
    fullEnvironment <- getEnvironment
    return (lookup x fullEnvironment)

if you don't mind going through the whole environment the whole time.

yatima2975
+3  A: 

Once you get that anything involving getEnv is going to involve returning a result in the IO monad, then there is nothing wrong with your basic approach. And while you could use System.IO.Error (and I would), it is just as valid, and instructive, to write it the way you did. However, you did use a bit more punctuation than idomatic Haskell would use:

getEnvVar x = (Just `fmap` getEnv x) `catch` const (return Nothing)

or

getEnvVar x = getEnv x `catch` const (return "")
MtnViewMark