Defining and Catching Exceptions

exception, haskell

Solution

What is best practice in Haskell is to leverage the awesome power of its type system to avoid needing to throw/catch exceptions for pure functions. There are cases where throwing an exception can actually make sense, but for something like your `toLower` function you can just choose to have a different return type. For example:

-- We can factor out our check for a non-alpha character
isNonAlpha :: Char -> Bool
isNonAlpha c = c' < 65 || (90 < c' && c' < 97) || 123 < c'
    where c' = ord c

-- Why throw an exception? Just return False
hasNonAlpha :: String -> Bool
hasNonAlpha str = any isNonAlpha str

-- Renamed to not conflict with Data.Char.toLower
myToLower :: String -> Maybe String
myToLower plaintext =
    if hasNonAlpha plaintext
        then Nothing
        else Just $ map toLower plaintext

Not only is this cleaner code, but now we don't have to worry about error handling at all, and someone else using your code won't get a nasty surprise. Instead, the notion of failure is encoded at the type level. To use this as an "error handling" mechanism, just work in the Maybe monad:

doSomething :: String -> String -> Maybe String
doSomething s1 s2 = do
    s1Lower <- myToLower s1
    s2Lower <- myToLower s2
    return $ s1Lower ++ s2Lower

If either `myToLower s1` or `myToLower s2` returns `Nothing`, then `doSomething` will return `Nothing`. There is no ambiguity, no chance for an unhandled exception, and no crashing at runtime. Haskell exceptions themselves, those thrown by the function `throw`, must be caught by `catch`, which has to execute in the `IO` monad. Without the `IO` monad, you can't catch exceptions. In pure functions, you can always represent the concept of failure with another data type without having to resort to `throw`, so there is no need to over-complicate code with it.

Alternative, you could have even written `myToLower` monadically as

import Control.Monad

-- Other code

myToLower :: String -> Maybe String
myToLower plaintext = do
    guard $ not $ hasNonAlpha plaintext
    return $ map toLower plaintext

The `guard` from `Control.Monad` acts as a sort of filter for `MonadPlus` instances. Since `Maybe` is an instance of `MonadPlus` (as are lists), this gives us very simple code.

Or, if you want to pass around an error message:

type MyError = String

myToLower :: String -> Either MyError String
myToLower plaintext = if hasNonAlpha plaintext
    then Left  $ "The string " ++ plaintext ++ " has non-alpha character(s)"
    else Right $ map toLower plaintext

Then you can change the type of `doSomething` to match:

doSomething :: String -> String -> Either MyError String
doSomething s1 s2 = do
    s1Lower <- myToLower s1
    s2Lower <- myToLower s2
    return $ s1Lower ++ s2Lower

If you notice, the monadic syntax lets us change the type signature of our function without even having to change the code! Play around with this implementation to get a feel for how it works.

Problem

I'm still getting the hang of haskell, and am trying to build my first "real" coding project, i.e. one where I'm going to including testing, write up documentation, etc. I'm sufficiently new to haskell where I know I don't have a lot of the knowledge of the language necessary so that everything I want to do is immediately within reach, but that's kind of the idea, so that the act of finishing will require that I touch most of the major pieces of the language. Anyway, so the current issue I'm having is regarding throwing and catching exceptions in the language, something that I understand can be done with quite varied approaches. I have a function here, toLower: ``` toLower :: String -> String toLower plaintext = if (catch (nonAlpha plaintext) handlerNonAlpha) then map charToLower plaintext else exitFailure ``` Which will take a string, throw an exception and exit if the string includes any non-alpha characters (so if not A-Z or a-z), or if not convert the string to lowercase. So what I have for the nonAlpha function is: ``` --- detect non-alpha character - throw error if existant data NonNumericException = NonNumException instance Exception NonNumericException handlerNonAlpha :: NonNumericException -> IO() handlerNonAlpha ex = putStrLn "Caught Exception: " ++ (show ex) ++ " - A non-alpha character was included in the plaintext." nonAlpha :: String -> Bool nonAlpha str = let nonalphas = [x | x <- str, (ord x) < 65 || (90 < (ord x) && (ord x) < 97) || 123 < (ord x)] in if (length nonalphas) == 0 then True else throw NonNumException ``` As I said I'm pretty new to haskell, so I'm a little vague on how this data/instance structure works, but as I understand it I'm defining an parent NonNumericException, of which NonNumException is a child (and I could have more), and in the instance line defining them to be Exceptions. The catch structure, if it detects an exception (for instance, when one is thrown at the end of nonAlpha if there is a non-alpha character), then calls the handler. So here are the compile errors that I get: ``` utilities.hs:61:3: Couldn't match expected type `[Char]' with actual type `IO ()' In the return type of a call of `putStrLn' In the first argument of `(++)', namely `putStrLn "Caught Exception: "' In the expression: putStrLn "Caught Exception: " ++ (show ex) ++ " - A non-alpha character was included in the plaintext." utilities.hs:61:3: Couldn't match expected type `IO ()' with actual type `[Char]' In the expression: putStrLn "Caught Exception: " ++ (show ex) ++ " - A non-alpha character was included in the plaintext." In an equation for `handlerNonAlpha': handlerNonAlpha ex = putStrLn "Caught Exception: " ++ (show ex) ++ " - A non-alpha character was included in the plaintext." utilities.hs:73:7: Couldn't match expected type `Bool' with actual type `IO ()' In the return type of a call of `catch' In the expression: (catch (nonAlpha plaintext) handlerNonAlpha) In the expression: if (catch (nonAlpha plaintext) handlerNonAlpha) then map charToLower plaintext else exitFailure utilities.hs:73:14: Couldn't match expected type `IO ()' with actual type `Bool' In the return type of a call of `nonAlpha' In the first argument of `catch', namely `(nonAlpha plaintext)' In the expression: (catch (nonAlpha plaintext) handlerNonAlpha) utilities.hs:75:8: Couldn't match type `IO a0' with `[Char]' Expected type: String Actual type: IO a0 In the expression: exitFailure In the expression: if (catch (nonAlpha plaintext) handlerNonAlpha) then map charToLower plaintext else exitFailure In an equation for `toLower': toLower plaintext = if (catch (nonAlpha plaintext) handlerNonAlpha) then map charToLower plaintext else exitFailure ``` So I guess my two question are, a) what's going wrong with the types for the handler (the line 61 errors), and b) how do I properly set the types for the functions that may throw an exception or exit with failure, but otherwise will return a bool or a string? EDIT: I guess I should note. I do see the similarities between this question and a number of others that have been asked. Part of what I'm looking for that I don't see is a description of what the structures here are actually doing, and what is best practice and why.

Original source