Kivételkezelés a Haskell 98 standard szerint csak az I/O könyvtárban van, de vannak kiegészítések, ahol ennél bővebben van rá lehetőség. Az I/O monadon belül elkaphatjuk és lekezelhetjük a kivételeket, amik a bemeneti/kimeneti műveletek során felléptek. A kivételes események az IOError adatosztályba tartoznak, ezért a kivételkezelést intéző catch függvény típusa:
Elfogás
catch :: IO a -> (IOError -> IO a) -> IO a
azaz két paramétere van: az "a" típusú ellenőrzendő függvény és egy kivételkezelő függvény, ami megadja, hogy adott kivétel esetén hogyan kell viselkednie. A típus termeszétesen nem változhat, így aztán ennek a függvénynek a visszatérési értéke ugyanúgy "a" típusú lesz. Például:
getChar' :: IO Char
getChar' = catch getChar (\e -> return '\n')
vagy kicsit olvashatóbban infix alakban:
getChar' = getChar `catch` (\e -> return '\n')
Itt a kivételkezelő minden e :: IOError eseményhez ugyanúgy a return '\n' függvényt rendeli hozzá (figyeljük meg, hogy a getChar és a return '\n' azonos típusú), ha csak egy adott eseményt akarunk lekezelni, akkor a többire az ioError függvényt használhatjuk, amivel továbbdobja a kivételt a következő kivételkezelőnek, pl.:
getChar' :: IO Char
getChar' = getChar `catch` eofHandler where
eofHandler e = if isEofError e then return '\n' else ioError e
A kivételek egymásba ágyazhatók, így a fent definiált getChar' segítségével:
getLine' :: IO String
getLine' = getLine'' `catch` (\err -> return ("Error: " ++ show err)) where
getLine'' = do c <- getChar'
if c == '\n' then return ""
else do l <- getLine'
return (c : l)
Ezzel a rekurzív definícióval a fájlvége hibát a getChar', minden más hibát pedig a getLine' kezel le. A kivételek ilyen módon "fölfelé" terjednek, ezért a kényelem kedvéért a Haskell 98-ban a legfelső szinten definiálva van egy kivételkezelő, ami kiírja a hibát és leáll. Kivétel dobására illetve új kivételek készítésére pillanatnyilag még nincs lehetőség. Kivételek általános megoldásáról érdekes ötleteket lehet olvasni ezen a levelezőlistán.
Az I/O monádon kívüli kivételkezelésre a bottomot (_|_) lehet még használni. Természetesen ez sem igazi kivétel, de hasonlóan alkalmazható. Ha a Haskellben egy kifejezésben hibaállapot - kivétel - jön létre, akkor a kifejezés eredménye _|_ lesz. Ez továbbadódik egyre feljebb az alkifejezésekből, végül eléri a legfelső szintet, ahol aztán hibajelzéssel leáll a program, ahogy azt a módszertan előírja. Számunkra akkor lenne használható ez a mechanizmus, ha a felfelé haladó _|_ elemet el tudnánk fogni. Csakhogy hogyan valósítható ez meg? Merthogy magához a _|_ elemhez nem lehet hozzányúlni, mert akkor már ennek a kifejezésnek is _|_ lesz az eredménye. Ráadásul a Haskell több beépített osztályának műveletei sem generálnak a következőkben leírandó kivételt, hanem egyszerűen a visszatérési értékükkel jelzik a művelet sikerességét.
Ugyanakkor egy függvény végrehajtása úgy zajlik, hogy ha a függvény első definíciója _|_ elemet ad vissza, akkor meg kell próbálkozni a továbbiak végrehajtásával:
length [] = 0
length (x:xs) = 1 + length xs
Ily módon mégiscsak lehetséges a _|_ elfogása.
Saját kivétel definiálása
Nincs még kész
Kivétel dobása
Hiányos
Példa:
throwTo :: ThreadId -> Exception -> IO ()
Kivételek terjedése
Nincs még kész
Kivételek lekezelése
Nincs még kész
Példa:
handle :: Exception e => (e -> IO a) -> IO a -> IO a
do handle (\e -> exitWith (ExitFailure 1)) $
...