۲۹ - ۶چرا 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 zerothrowIO هم مثلِ 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 نحوهی تعریف و انداختنِ استثناها رو تغییر داده، ولی با این حال مثالها باید مفید باشن.