Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How does the presence of the "error" function bear on the purity of Haskell?

I've always wondered how the Haskell exception system fits in with the whole "Pure functional language" thing. For example see the below GHCi session.

GHCi, version 8.0.1: http://www.haskell.org/ghc/  :? for help
Prelude> head []
*** Exception: Prelude.head: empty list
Prelude> :t head
head :: [a] -> a
Prelude> :t error
error :: [Char] -> a
Prelude> error "ranch"
*** Exception: ranch
CallStack (from HasCallStack):
  error, called at <interactive>:4:1 in interactive:Ghci1
Prelude>

The type of head is [a] -> a. But when you call it on the special case of an empty list, you get an exception instead. But this exception is not accounted for in the type signature.

If I remember correctly it's a similar story when there is a failure during pattern matching. It doesn't matter what the type signature says, if you haven't accounted for every possible pattern, you run the risk of throwing an exception.

I don't have a single, concise question to ask, but my head is swimming. What was the motivation for adding this strange exception system to an otherwise pure and elegant language? Is it still pure but I'm just missing something? If I want to take advantage of this exception feature, how would I go about doing it (ie how do I catch and handle exceptions? is there anything else I can do with them?) For example, if ever I write code that uses the "head" function, surely I should take precautions for the case where an empty list somehow smuggles itself in.

like image 945
TheIronKnuckle Avatar asked Oct 17 '16 00:10

TheIronKnuckle


1 Answers

You are confusing two concepts: purity and totality.

  • Purity says that functions have no side effects.
  • Totality says that every function terminates and produces a value.

Haskell is pure, but is not total.

Outside of IO, nontermination (e.g., let loop = loop in loop) and exceptions (e.g., error "urk!") are the same – nonterminating and exceptional terms, when forced, do not evaluate to a value. The designers of Haskell wanted a Turing-complete language, which – as per the halting problem – means that they forwent totality. And once you have nontermination, I suppose you might as well have exceptions, too – defining error msg = error msg and having calls to error do nothing forever is much less satisfying in practice than actually seeing the error message you want in finite time!

In general, though, you're right – partial functions (those which are not defined for every input value, like head) are ugly. Modern Haskell generally prefers writing total functions instead by returning Maybe or Either values, e.g.

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

errHead :: [a] -> Either String a
errHead []    = Left "Prelude.head: empty list"
errHead (x:_) = Right x

In this case, the Functor, Applicative, Monad, MonadError, Foldable, Traversable, etc., machinery makes combining these total functions and working with their results easy.

Should you actually come across an exception in your code – for instance, you might use error to check a complicated invariant in your code that you think you've enforced, but you have a bug – you can catch it in IO. Which returns to the question of why it's OK to interact with exceptions in IO – doesn't that make the language impure? The answer is the same as that to the question of why we can do I/O in IO, or work with mutable variables – evaluating a value of type IO A doesn't produce the side effects that it describes, it's just an action that describes what a program could do. (There are better descriptions of this elsewhere on the internet; exceptions aren't any different than other effects.)

(Also, note that there is a separate-but-related exception system in IO, which is used when e.g. trying to read a file that isn't there. People are often OK with this exception system, in moderation, because since you're in IO you're already working with impure code.)

like image 81
Antal Spector-Zabusky Avatar answered Sep 27 '22 19:09

Antal Spector-Zabusky