[Start on ch20 John Goerzen **20080429054754] { adddir ./examples/ch20 hunk ./en/ch20-errors.xml 6 - FIXME + + Error handling is one of the most important -- and overlooked -- + topics for programmers, regardless of the language used. In + Haskell, you will find two major types of error handling employed: + "pure" error handling and exceptions. + + + When we speak of "pure" error handling, we are referring to + algorithms that do not require anything from the &IO; monad. + We can often implement error handling for them by simply using + Haskell's expressive data type system to our advantage. Haskell + also has an exception system. Due to the complexities of lazy + evaluation, exceptions in Haskell can be raised anywhere, but only + caught within the &IO; monad. In this chapter, we'll consider + both. + + + Error Handling with Data Types + + Let's begin our discussion of error handling with a very simple + function. Let's say that we wish to perform division on a + series of numbers. We have a constant numerator, but wish to + vary the denominator. We might come up with a function like + this: + + &divby1.hs:all; + + Very simple, right? We can play around with this a bit in + &ghci;: + + &divby1.ghci:ex1; + + This behaves as expected: 50 / 1 is + 50, 50 / 2 is + 25, and so forth. + + We're using integral division here, so 50 / + 8 shows as 6 instead of + 6.25. We're not using floating-point + arithmetic in this example because division by zero with a + &Double; produces the special value + Infinity rather than an error. + + This even worked with the infinite list + [1..]. + What happens if we sneak a + 0 into our list somewhere? + + &divby1.ghci:ex2; + + Isn't that interesting? &ghci; started displaying the output, + then stopped when an exception when it got to the zero. That's + lazy evaluation at work -- it calculated results as needed. + + + As we will see later in this chapter, in the absence of an + explicit exception handler, this exception will crash the + program. That's obviously not desirable, so let's consider + better ways we could indicate an error in this pure function. + + + Use of Maybe + + One immediately-recognizable easy way to indicate failure is + to use &Maybe;. Instead of just returning a list and raising + an exception on failure, we can return &Nothing; if the input + list contained a zero anywhere, or &Just; with the results + otherwise. Here's an implementation of such an algorithm: + + &divby2.hs:all; + + If you try it out in &ghci;, you'll see that it works: + + &divby2.ghci:ex1; + + The function that calls divBy can now use a + case statement to see if the call was + successful, just as divBy does when it + calls itself. + + + Loss and Preservation of Laziness + + The use of &Maybe; was convenient, but has come at a cost. + divBy can no longer handle infinite lists + as input. Since the result is Maybe [a], + the entire input list must be examined before we can be sure + that we won't be returning Nothing due to a + zero somewhere in it. You can verify this is the case by + attempting one of our earlier examples: + + &divby2.ghci:ex2; + + Note that you don't start seeing partial output here; you get + no output. Notice that at each step in + divBy (except for the case of an empty + input list or a zero at the start of the list), the results + from every subsequent element must be known before the results + from the current element can be known. Thus this algorithm + can't work on infinite lists, and it is also not very + space-efficient for large finite lists. + + + Having said all that, &Maybe; is often a fine choice. In this + particular case, we don't know whether there will be a problem + until we get into evaluating the entire input. Sometimes we + know of a problem up front. For instance, saying + tail [] in &ghci; produces an exception. + We could easly write an infinite-capable &tail; that doesn't + have this problem: + + &safetail.hs:all; + + This simply returns &Nothing; if given an empty input list, + or &Just; with the result for anything else. Since we only + have to make sure the list is non-empty before knowing + whether or not we have an error, using &Maybe; here doesn't + reduce our laziness. We can test this out in &ghci; and see how it compares with + regular &tail;: + + &safetail.ghci:ex1; + + Here, we can see our safeTail performed + as expected. But what about infinite lists? We don't want + to print out an infinite number of results, so we can test + with take 5 (tail [1..]) and a similar + construction with safeTail: + + &safetail.ghci:ex2; + + Here you can see that both &tail; and + safeTail handled infinite lists just + fine. Note that we were able to deal better with an empty + input list; instead of raising an exception, we decided to + return &Nothing; in that situation. We were able to achieve + error handling at no expense to laziness. + + + But how to apply this to our divBy + example? Let's consider the situation there: failure is a + property of an individual bad input, not of the input list + itself. How about making failure a property of an + individual output element, rather than the output list + itself? That is, instead of a function of type a + -> [a] -> Maybe [a], instead we will have + a -> [a] -> [Maybe a]. This will have + the benefit of preserving laziness, plus the caller will be + able to determine exactly where in the list the problem was + -- or even just filter out the problem results if desired. + Here's an implementation: + + &divby3.hs:all; + + + + addfile ./examples/ch20/divby1.ghci hunk ./examples/ch20/divby1.ghci 1 +:l divby1.hs +--# ex1 +divBy 50 [1,2,5,8,10] +take 5 (divBy 100 [1..]) +--# ex2 +divBy 50 [1,2,0,8,10] addfile ./examples/ch20/divby1.hs hunk ./examples/ch20/divby1.hs 1 +{-- snippet all --} +-- ch20/divby1.hs + +divBy :: Integral a => a -> [a] -> [a] +divBy numerator denominators = + map (\x -> numerator `div` x) denominators +{-- /snippet all --} addfile ./examples/ch20/divby2.ghci hunk ./examples/ch20/divby2.ghci 1 +:l divby2.hs +--# ex1 +divBy 50 [1,2,5,8,10] +divBy 50 [1,2,0,8,10] +--# ex2 +divBy 100 [1..] addfile ./examples/ch20/divby2.hs hunk ./examples/ch20/divby2.hs 1 +{-- snippet all --} +-- ch20/divby2.hs + +divBy :: Integral a => a -> [a] -> Maybe [a] +divBy _ [] = Just [] +divBy _ (0:_) = Nothing +divBy numerator (denom:xs) = + case divBy numerator xs of + Nothing -> Nothing + Just results -> Just ((numerator `div` denom) : results) +{-- /snippet all --} addfile ./examples/ch20/divby3.hs hunk ./examples/ch20/divby3.hs 1 +{-- snippet all --} +-- ch20/divby3.hs + +divBy :: Integral a => a -> [a] -> [Maybe a] +divBy numerator denominators = + map worker denominators + where worker 0 = Nothing + worker x = Just (numerator `div` x) +{-- /snippet all --} addfile ./examples/ch20/safetail.ghci hunk ./examples/ch20/safetail.ghci 1 +:l safetail.hs +--# ex1 +tail [1,2,3,4,5] +safeTail [1,2,3,4,5] +tail [] +safeTail [] +--# ex2 +take 5 (tail [1..]) +case safeTail [1..] of {Nothing -> Nothing; Just x -> Just (take 5 x)} +take 5 (tail []) +case safeTail [] of {Nothing -> Nothing; Just x -> Just (take 5 x)} addfile ./examples/ch20/safetail.hs hunk ./examples/ch20/safetail.hs 1 +{-- snippet all --} +-- ch20/safetail.hs + +safeTail :: [a] -> Maybe [a] +safeTail [] = Nothing +safeTail (_:xs) = Just xs +{-- /snippet all --} }