۲۹ - ۶چرا throwIO بهدرد میخوره؟
شاید وجودِ چیزی مثلِ thowIO
به نظرتون عجیب اومده باشه (شاید هم نه!). اصلاً چرا بخوایم یه برنامه رو عمداً با یه استثنا متوقف کنیم؟ در دنیای واقعی زیاد لازم میشه که بخوایم در صورتِ محیا شدنِ شرایطی، برنامه متوقف بشه؛ اما شاید دیدنِ چنین چیزی از مثالهایی که تا اینجا زدیم آسون نباشه.
تابعی به اسمِ throw
وجود داره که اون هم استثناهایی مثلِ ArithException
میندازه، اما به ندرت استفاده میشه. چیزیه که به تابعِ div
امکانِ انداختنِ یه DivideByZero
میده، اما خارج از این تابعهای کتابخونهای، لازم نمیشه.
میشه فرقِ بین throw
و throwIO
رو در تایپ دید:
throwIO :: Exception e => e -> IO a
میشه این "تبعیضی" که در فرمِ انداختنِ یه استثنا قائل میشیم رو یه اثر دونست. راه متعارف برای انداختنِ یه استثنا، استفاده از throwIO
ِه، که در جوابش IO
داره. تنها تفاوتش با throw
همینه که استثنا رو در IO
میپوشونه. استثناها همیشه در IO
مدیریت میشن.* مدیریتِ استثناها باید داخلِ IO
انجام بشه، حتی اگه بدون یه تایپِ IO
انداخته شده باشن. تقریباً هیچ وقت throw
لازم نمیشه، چون بدونِ هیچ هشداری در تایپ (حتی IO
)، استثنا رو میندازه.
چرا؟ چون گرفتن و مدیریتِ استثناها معادلِ اینه که از یک ورودی، میشه جوابهای مختلفی تولید کرد. یعنی شفافیتِ مرجع رو نقض میکنه.
یه مثال میزنیم که توی IO
همینطوری یه استثنا انداخته میشه تا تأثیرش روی جریانِ برنامه رو ببینین:
import Control.Exception
main :: IO ()
main = do
throwIO DivideByZero
putStrLn "lol"
Prelude> main
*** Exception: divide by zero
throwIO
هم مثلِ throw
معمولاً در پشتِ صحنه توسط تابعهای کتابخونهای صدا زده میشه. در تعامل با دنیای واقعی، اکثراً لازم میشه در شرایطِ بخصوصی، برنامه وایسه یا یه پیغامِ خطا بده و بگه که یه چیزهایی درست پیش نرفتن. به دوتا مثال از کدهای واقعی نگاه میکنیم (از کتابخونه ِ http-client
توسطِ مایکل اسنویمن) که وقتی چیزهای http
همونطوری که میخواستیم پیش نرفتن، با استفاده از throwIO
استثنا میندازه:
connectionReadLine :: Connection
-> IO ByteString
connectionReadLine conn = do
bs <- connectionRead conn
when (S.null bs) $
throwIO IncompleteHeaders
connectionReadLineWith conn bs
در کُدِ بالا، وقتی ByteString
خالی باشه، throwIO
یه استثنا ِ IncompleteHeaders
میندازه. در مثالِ بعدی، وقتی پاسخ خیلی طول بکشه (یا time out بشه)، یه استثنا ِ ResponseTimeout
میندازه:
parseStatusHeaders :: Connection
-> Maybe Int
-> Maybe (IO ())
-> IO StatusHeaders
parseStatusHeaders conn timeout' cont
| Just k <- cont =
getStatusExcpectContinue k
| otherwise =
getStatus
where
withTimeout = case timeout' of
Nothing -> id
Just t ->
timeout t >=>
maybe
(throwIO ResponseTimeout)
return
-- ... بقیهی کد رو حذف کردیم ...
بدونِ دونستنِ نحوهی پیادهسازیِ استثناها هم میشه از http-client
استفاده کرد. ولی گاهی اوقات هم چنین چیزی لازم میشه، پس در بخشِ بعد به ساختنِ تایپهای استثنای خودمون نگاه کنیم. فقط دقت کنین که از لحظهی نوشتن این کتاب، http-client
نحوهی تعریف و انداختنِ استثناها رو تغییر داده، ولی با این حال مثالها باید مفید باشن.