There is no way to use catch
in pure code. This is by design: exceptions are handled by the IO system. That's why the type of catch
uses IO
. If you want to handle failure in pure code, you should use a type to represent the possibility of failure. In this case, the failure is that the value can sometimes not exist.
The type we use in Haskell to denote a value that can either exist or not is called Maybe
. Maybe
is an instance of Monad
, so it is "monadic", but that should not dissuade you from using it for its intended purpose. So the function you want is headMay
from the safe package: headMay :: [a] -> Maybe a
.
That said, if you want to avoid monads you can instead use a function that unpacks the list:
listElim :: b -> (a -> [a] -> b) -> [a] -> b
listElim nil _ [] = nil
listElim _ cons (x:xs) = cons x xs
As you can see, this replaces a []
with nil
and a :
with a call to cons
. Now you can write a safe head that lets you specify a default:
headDef :: a -> [a] -> a
headDef def = listElim def const
Unfortunately, functions are an instance of Monad
, so it turns out that you have been "force[d] to be monadic" after all! There is truly no escaping the monad, so perhaps it is better to learn how to use them productively instead.