Reputation: 730
{-# 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
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
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
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
Upvotes: 6