nvrandow
nvrandow

Reputation: 730

Haskell: Return Nothing in case of exception

{-# LANGUAGE DeriveDataTypeable, ScopedTypeVariables #-}

import Data.Typeable
import Control.Exception

data EmptyListException = EmptyListException
    deriving (Show, Typeable)
instance Exception EmptyListException

myHead :: [a] -> a
myHead []    = throw EmptyListException
myHead (x:_) = x

mySafeHead :: [a] -> IO (Maybe a)
mySafeHead xs = (return (Just (myHead xs)))
                `catch`
                (\(ex::EmptyListException) -> return Nothing)

I want to return the first element of xs if there is one. Otherwise I want to return "Nothing", but it returns the Exception wrapped in "Just". Why is that? P.S.: I have to use myHead in mySaveHead.

Upvotes: 2

Views: 1937

Answers (3)

frasertweedale
frasertweedale

Reputation: 5664

If all you want is to get the head of a list as Just a when the list is non-empty, otherwise Nothing, the sensible approach is not to use exceptions at all (whether explicitly thrown by you, or thrown as a result of calling head :: [a] -> a on an empty list). Instead, define a total function:

mySafeHead :: [a] -> Maybe a
mySafeHead [] = Nothing
mySafeHead (a:_) = Just a

Alternatively, use headMay from the safe package.

The existence of non-total functions in the Prelude such as head and tail is an historical mistake. Hopefully one day they will be deprecated and, eventually, removed.

Upvotes: 1

rampion
rampion

Reputation: 89093

So when I run your code, this is what I see

λ mySafeHead []
Just *** Exception: EmptyListException

I understand why you'd describe this as "it returns the Exception wrapped in "Just".", but that's not actually what's going on.

Haskell is non-strict, so it postpones computation until a value is demanded.

In mySafeHead the value of myHead xs is not examined, so it isn't evaluated. Instead, the computation for that value is left as a thunk, which is wrapped in Just and returned.

Then, in ghci, that thunk is finally forced when trying to print the value, and an exception is raised. Since we're now well outside the scope of the catch statement, it doesn't apply, and the exception makes it all the way to the terminal, where it interrupts the printing of the output.

The easy way to fix this is to use seq to force evaluation of myHead xs before exiting the catch statement:

mySafeHead' :: [a] -> IO (Maybe a)                             
mySafeHead' xs = (let x = myHead xs in x `seq` return (Just x))
                `catch`                                        
                (\(_::EmptyListException) -> return Nothing)   

seq takes two arguments - it returns the second, but only after forcing the first to Weak Head Normal Form (WHNF), that is, after finding out what the outermost constructor is. This forces x sufficiently for the EmptyListException to be raised, so catch can do its thing:

λ mySafeHead' []
Nothing

Upvotes: 7

Shoe
Shoe

Reputation: 76280

You can use evaluate for catching exceptions while executing pure computations:

mySafeHead :: [a] -> IO (Maybe a)
mySafeHead xs = mySafeHead' xs `catch` handler
    where  
        mySafeHead' :: [a] -> IO (Maybe a)
        mySafeHead' ls = do
            x <- evaluate $ myHead ls
            return $ Just x
        handler :: EmptyListException -> IO (Maybe a)
        handler ex = return Nothing

Live demo

Upvotes: 6

Related Questions