۲۹ - ۷ساختِ استثناهای خودمون
اکثراً تایپهای استثنای خودمون رو لازم داریم، مثلِ 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)به درد میخوره، نه؟ خلاصه اینکه در طراحیِ تایپهای خطا، از همون روشها و قضاوتهایی که برای تایپهای معمولی استفاده میکنین، بهره ببرین. محتوا رو حفظ کنین تا هرکسی بتونه از روی تایپها بفهمه چه مشکلی رو میخواستین برطرف کنین.