Errors and exceptions

Contents
Partial functions
throw and catch
Exception types
Which error style

Partial functions

A function that crashes on some inputs is partial. The two standard ways to crash are error for a real bug and undefined for a placeholder:

head [] -- exception: empty list error "unreachable" -- exception with a message undefined -- crashes when forced

undefined is useful while writing a skeleton, because the program compiles and fails only where the hole is actually evaluated. Leaving one in production is the classic Haskell incident.

throw and catch

Exceptions in IO are caught by type. A missing file raises an IOException, and catch handles it:

import Control.Exception safeRead :: FilePath -> IO (Maybe String) safeRead path = (Just <$> readFile path) `catch` \e -> do hPutStrLn stderr (show (e :: IOException)) pure Nothing

The type annotation on e is required, because the handler has to say which exceptions it claims to understand. A handler that catches everything is a bug waiting to happen.

Exception types

Any type can be thrown if it implements Exception. For application errors a small sum type is clearer than a string:

data TodoError = NotFound Int | BadInput String deriving (Show) instance Exception TodoError removeItem i items | i < 0 = throwIO (BadInput "negative index") | otherwise = deleteAt i items

The empty instance is enough, because the class provides the rest. Now a caller can catch TodoError without catching a disk failure by accident.

Which error style

Three styles coexist for good reasons. Pick by where the decision belongs:

-- expected failure, caller decides: Either parse :: String -> Either String Int -- absent value, no reason needed: Maybe find :: Int -> [Int] -> Maybe Int -- environmental failure, cleanup needed: IO exceptions readConfig :: FilePath -> IO Config

error is reserved for broken invariants that the type system cannot express. If a caller could recover, the signature should say so.

Next: Concurrency

Article author: Arthur Isaev

Related articles
Haskell
Haskell course: zero to hero in 50 lessons
What is Haskell and why it matters
Install the toolchain with GHCup
GHCi: the interactive shell
Your first program
Values with let and where
Types and type signatures
Functions and application
Currying and partial application
Operators, sections, and fixity
if, guards, and pattern guards
case expressions
Pattern matching
Tuples
Lists
Ranges and enumeration
List comprehensions
Recursion replaces loops
map, filter, and foldr
Lambdas and composition
Type classes
Parametric polymorphism
Maybe and null safety
Either and recoverable errors
data: product types
data: sum types
Records
newtype and type synonyms
Recursive data: binary trees
Functor
Applicative
Monad
do notation
IO and purity
Files and IO
Modules and imports
Cabal projects
Dependencies
Testing with HUnit
Property testing with QuickCheck
Laziness and infinite lists
Strictness and space leaks
Folds
Monoids
Traversable
Monad transformers
Errors and exceptions
Concurrency
Parsing with Megaparsec
Capstone: a todo CLI
Hero roadmap: the whole course on one page
Banner image

Search this site

Channel @aofeed Chat @aofeedchat

Contacts and cooperation:
I recommend our hosting beget.com
Write to info@aredel.com if you:
1. Want to write an article for our site or translate an article into your native language.
2. Want to place thematically relevant ads on the site.
3. Ads on my site pass maximum censorship. If you see an ad block unsuitable for school-age children, shocking or misleading - please contact us by e-mail
4. Found a mistake, inaccuracy, bug, etc. on the site. ... .......
5. Articles can be shared on social media by clicking a network icon: