۱۶ - ۱۴فانکتورِ IO یا IO Functor
تایپِ IO
رو قبلاً در فصلهای ساختِ پروژه و تستینگ دیدیم، ولی به غیر از چاپ ِ نوشته و گرفتنِ ورودی از کاربر کارِ زیادی باهاش نمیکردیم. جلوتر در کتاب یه فصلِ کامل برای IO
هست. این تایپ، یه نوعداده ِ انتزاعی ِه؛ هیچ دادهسازی که بشه روش تطبیق الگو کرد نداره، پس تنها راهی که میشه با مقادیرِ تایپِ IO a
تعامل کرد، از طریقِ تایپکلاسهاییه که ارائه میده. یکی از سادهترینهاشون Funtor
ِه:
-- getLine :: IO String
-- read :: Read a => String -> a
getInt :: IO Int
getInt = fmap read getLine
تایپِ Int
یه نمونه ِ Read
داره، و fmap
تابعِ read
رو از روی تایپِ IO
لیفت میکنه. میشه گفت getLine
یک راه برای بدست آوردنِ یه نوشته هست، خودش String
نیست. معنیِ IO
اینه که ممکنه با اثر باشه، ولی لزوماً اینطور نیست. اینجا عوارض جانبی لازمه تا بلوکه کنه و منتظرِ ورودیِ کاربر از طریقِ جریانِ ورودیِ استاندارد ای که سیستم عامل تأمین میکنه باشه:
Prelude> getInt
10
10
نوشتیم ۱۰ و enter زدیم. GHCi مقادیرِ IO
رو چاپ میکنه، مگر اینکه تایپشون IO ()
باشه (که مقدارِ واحد رو مخفی میکنه چون بیمعنیه):
Prelude> fmap (const ()) getInt
10
اون ۱۰ که بالا نوشته شده، ۱۰ ایه که ما تایپ کردیم و enter زدیم. GHCi بعد از اون چیزی رو چاپ نمیکنه چون اون مقدارِ Int
که از String
گرفتیم رو با ()
عوض کردیم (از طریق اعمال ِ const ()
به محتویاتِ IO Int
). اگه حضورِ IO
رو نادیده بگیریم، مثل این میشه:
Prelude> let getInt = 10 :: Int
Prelude> const () getInt
()
GHCi برای راحتی، هیچ مقدارِ IO ()
رو، با این فرض که اجراییه ِ IO
برای اثراتش محاسبه شده بوده، چاپ نمیکنه. دلیل دیگهش هم بیفایده بودنشه؛ مقدار واحد اطلاعاتی با خودش نداره. میشه با استفاده از تابعِ return
(که قبلاً دیدیم، اما بعداً توضیح میدیم)، یه مقدار واحد رو به داخلِ IO
لیفت کنیم و این رفتارِ GHCi رو بازسازی کنیم:
Prelude> return 1 :: IO Int
1
Prelude> ()
()
Prelude> return () :: IO ()
Prelude>
اگه کار مفیدتری بخوایم انجام بدیم چطور؟ میشه هر تابعی رو با fmap
از روی IO
لیفت کنیم:
Prelude> fmap (+1) getInt
10
11
Prelude> fmap (++ " and me too!") getLine
hello
"hello and me too!"
همون کاری که با Functor
انجام دادیم رو با گرامر ِ do
هم میشه انجام داد:
meTooIsm :: IO String
meTooIsm = do
input <- getLine
return (input ++ "and me too!")
bumpIt :: IO Int
bumpIt = do
intVal <- getInt
return (intVal + 1)
اگه fmap f
برای کاری که میخواین انجام بدین کفایت میکنه، معمولاً اونطوری کوتاهتر و واضحتر میشه. این که اول کُدتون رو با نسخهی "وراجتر" یا طولانیترِ بعضی بیانیه شروع کنین و بعد از اینکه کار کرد خلاصهش کنین، روندِ خیلی رایجیه و هیچ ایرادی نداره.