A Haskell programozási nyelv

Kivételkezelés

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)) $ ...