۲۹ - ۷ساختِ استثناهای خودمون
اکثراً تایپهای استثنای خودمون رو لازم داریم، مثلِ http-client
. اینطوری اتفاقاتی که توی برنامه میوفتن رو دقیقتر مشخص میکنیم. برای دیدنِ طرزِ انجام این کار، با یه مثال کوچیک، یکی از دو خطا ِ ممکنِ یه تابعِ ساده رو ساتع کنیم:
module OurExceptions where
import Control.Exception
data NotDivThree =
NotDivThree
deriving (Eq, Show)
instance Exception NotDivThree
data NotEven =
NotEven
deriving (Eq, Show)
instance Exception NotEven
دقت کنین که نمونههای Exception
قابلِ مشتقگیری اند – لازم نیست خودتون بنویسین. ادامه بدیم:
evenAndThreeDiv :: Int -> IO Int
evenAndThreeDiv i
| rem i 3 /= 0 = throwIO NotDivThree
| odd i = throwIO NotEven
| otherwise = return i
بعد شرایط موفقیت و خطا رو میشه دید:
*OurExceptions> evenAndThreeDiv 0
0
*OurExceptions> evenAndThreeDiv 1
*** Exception: NotDivThree
*OurExceptions> evenAndThreeDiv 2
*** Exception: NotDivThree
*OurExceptions> evenAndThreeDiv 3
*** Exception: NotEven
*OurExceptions> evenAndThreeDiv 6
6
*OurExceptions> evenAndThreeDiv 9
*** Exception: NotEven
*OurExceptions> evenAndThreeDiv 12
12
با اینکه چنین کُدی رایجه، اما یه مشکلی داره. اگه بخوایم بدونیم چه ورودی یا ورودیهایی عاملِ خطا شدن چطور؟ باید محتوا اضافه کنیم!
اضافه کردنِ محتوا
بریم سراغش:
module OurExceptions where
import Control.Exception
data NotDivThree =
NotDivThree Int
deriving (Eq, Show)
instance Exception NotDivThree
data NotEven =
NotEven Int
deriving (Eq, Show)
instance Exception NotEven
evenAndThreeDiv :: Int -> IO Int
evenAndThreeDiv i
| rem i 3 /= 0 = throwIO (NotDivThree i)
| odd i = throwIO (NotEven i)
| otherwise = return i
حالا وقتی خطا بگیریم، میدونیم چه ورودیای باعثش شده:
*OurExceptions> evenAndThreeDiv 12
12
*OurExceptions> evenAndThreeDiv 9
*** Exception: NotEven 9
*OurExceptions> evenAndThreeDiv 8
*** Exception: NotDivThree 8
*OurExceptions> evenAndThreeDiv 3
*** Exception: NotEven 3
*OurExceptions> evenAndThreeDiv 2
*** Exception: NotDivThree 2
یکی رو بگیر، همه رو بگیر
حالا احتمالاً میدونین چطوری این دوتا خطا رو بگیریم:
catchNotDivThree :: IO Int
-> (NotDivThree -> IO Int)
-> IO Int
catchNotDivThree = catch
catchNotEven :: IO Int
-> (NotEven -> IO Int)
-> IO Int
catchNotEven = catch
یا با try
:
Prelude> type EA e = IO (Either e Int)
Prelude> try (evenAndThreeDiv 2) :: EA NotEven
*** Exception: NotDivThree 2
Prelude> try (evenAndThreeDiv 2) :: EA NotDivThree
Left (NotDivThree 2)
اون تایپِ مستعار مفهومی نداره، فقط یه کم شلوغیها رو کم میکنه. حالا هردو خطاها رو با تابعِ catches
میشه مدیریت کرد:
catches :: IO a -> [Handler a] -> IO a
catchBoth :: IO Int -> IO Int
catchBoth ioInt =
catches ioInt
[ Handler
(\(NotEven _) -> return maxBound)
, Handler
(\(NotDivThree _) -> return minBound)
]
استفاده از maxBound
یا minBound
در کُدِ واقعی خوب نیست. اتفاقاً، تایپِ Handler
هم از همون کلکی که تایپِ SomeException
برای مخفی کردن آرگومانهای تایپی استفاده میکنه، برای پوشوندنِ مقادیر توی اون لیستِ هندلِرهای استثنا استفاده میکنه: سورِ وجودی.
data Handler a where
Handler :: Exception e
=> (e -> IO a) -> Handler a
-- Control.Exception تعریف شده در
به این خاطر میتونیم یه لیست از هندلرهایی که استثناهای متنوعی رو هندل میکنن درست کنیم چون تایپهای استثنا، تحتِ نوعداده ِ Handler
سورِ وجودی دارن.
ولی اگه این به اندازهی کافی راحت نباشه چطور؟ اگه یه خانواده از استثناهای مرتبط یا مشابه داشته باشیم که بخوایم گروهی بگیریم چطور؟ باید بریم سراغِ دوستِ قدیمیمون، تایپِ جمع!
module OurExceptions where
import Control.Exception
data EATD =
NotEven Int
| NotDivThree Int
deriving (Eq, Show)
instance Exception EATD
evenAndThreeDiv :: Int -> IO Int
evenAndThreeDiv i
| rem i 3 /= 0 = throwIO (NotDivThree i)
| odd i = throwIO (NotEven i)
| otherwise = return i
حالا برای گرفتنِ هر دو خطا فقط یه عهدهگیرنده لازم داریم. بعدش هم میتونیم مثل نوعدادههای معمولی، روی تایپهای استثنا تطبیق الگو کنیم:
Prelude> type EA e = IO (Either e Int)
Prelude> try (evenAndThreeDiv 0) :: EA EATD
Left (NotEven 0)
Prelude> try (evenAndThreeDiv 1) :: EA EATD
Left (NotDivThree 1)
به درد میخوره، نه؟ خلاصه اینکه در طراحیِ تایپهای خطا، از همون روشها و قضاوتهایی که برای تایپهای معمولی استفاده میکنین، بهره ببرین. محتوا رو حفظ کنین تا هرکسی بتونه از روی تایپها بفهمه چه مشکلی رو میخواستین برطرف کنین.