۱۳ - ۷تعاملیسازی برنامه
حالا میخوایم کاری کنیم که برنامه اول اسم ِتون رو بپرسه، بعد هم بهتون سلام کنه. اول تابعِ sayHello رو با یه آرگومان بازنویسی میکنیم:
sayHello :: String -> IO ()
sayHello name = do
putStrLn ("Hi " ++ name ++ "!")دقت کنین که الحاق ِ Stringها داخل پرانتز، آرگومانِ putStrLn ِه.
بعد هم main رو طوری تغییر میدیم که اسم ِ کاربر رو بگیره:
-- src/Main.hs
main :: IO ()
main = do
name <- getLine
sayHello name
dogsچندتا چیز جدیداند. اینجا از گرامر ِ do استفاده کردیم که در واقع شکر گرامری ِه. از do داخلِ توابعیِ که IO برمیگردونن استفاده میکنیم تا عوارضِ جانبی رو با یه گرامر ِ مناسب متسلسل کنیم. کُدِ بالا رو جزبهجز بررسی میکنیم:
main :: IO ()
main = do
-- [1]
name <- getLine
-- [4] [3] [2]
sayHello name
-- [5]
dogs
-- [6]۱.
کلیدواژه ِ do، بلوک ِ do رو شروع میکنه.
۲.
getLine تایپِ IO String داره، چون برای بدست آوردنِ String باید I/O (ورودی/خروجی، عوارض جانبی) انجام بده. همون چیزیه که میذاره اسم ِتون رو بنویسین تا در تابعِ main استفاده بشه.
۳.
علامتِ <- در یه بلوک ِ do، انقیاد یا بایند خونده میشه. در فصلهایی که از Monad و IO صحبت میکنیم، توضیح میدیم که این چی هست و چطور کار میکنه.
۴.
نتیجهی بایند کردن (<-) از IO String، تایپِ String میده. که اون رو به متغیرِ name مقیّد کردیم. به خاطر داشته باشین که getLine تایپش IO String، و name تایپش String ِه.
۵.
تابعِ sayHello انتظار یه آرگومانِ String داره، که name چنین تایپی داره، اما getLine اینطور نیست.
۶.
dogs انتظارِ هیچ چیز رو نداره.* یه اجراییه ِ IO با تایپِ IO () هست که با تایپِ کلیِ main سازگاره.
مثل سگهای واقعی.
حالا میسازیم:
$ stack buildو برنامه رو اجرا میکنیم:
$ stack exec helloبعد از اینکه دکمهی enter رو میزنین، برنامه منتظرِ ورودی ِ شما میشه. خطِ ترمینال چشمک میزنه و منتظر میشه تا اسم ِتون رو وارد کنین. اسم ِتون رو که بنویسین و enter بزنین، بهتون سلام میکنه و قربون صدقهی یه هاپو میره.
اگه getLine رو به sayHello میدادیم چی؟
اگه main رو بدون گرامر ِ do مینوشتیم، یعنی بدونِ <- مثلِ زیر
main :: IO ()
main = sayHello getLineخطای تایپ ِ زیر رو میگرفتیم:
$ stack build
[2 of 2] Compiling Main
src/Main.hs:8:17:
Couldn't match type ‘IO String’ with ‘[Char]’
Expected type: String
Actual type: IO String
In the first argument of ‘sayHello’,
namely ‘getLine’
In the expression: sayHello getLineدلیلش اینه که getLine یه اجراییه ِ IO با تایپِ IO String ِه، اما sayHello انتظارِ یه مقدار با تایپِ String داره. باید با استفاده از <-، از روی IO بایند کنیم تا نوشته ای که برای sayHello میخوایم رو بگیریم. اینها رو با جزئیات بیشتر توضیح میدیم – در یکی از فصلها یه کم توضیح میدیم، در یکی دیگه از فصلها خیلی بیشتر.
اضافه کردنِ prompt
میشه با اضافه کردنِ یه prompt که به کاربر بگه برنامه منتظرِ ورودیه، استفاده از برنامه رو راحتتر کرد! باید main رو تغییر بدیم:
module Main where
import DogsRule
import Hello
import System.IO
main :: IO ()
main = do
hSetBuffering stdout NoBuffering
putStr "Please input your name: "
name <- getLine
sayHello name
dogsخیلی کارها کردیم. یکی اینکه بجای putStrLn از putStr استفاده کردیم تا ورودی جلوی prompt بیاد. System.IO رو هم وارد کردیم تا از hSetBuffering، stdout، و NoBuffering استفاده کنیم. اون خط اول در بلوک ِ do کاری میکنه که putStr بافِر نشه (به تعویق نیوفته) و بیوقفه چاپ شه.* اگه برنامه رو دوباره بسازین و اجرا کنین، اینطور کار میکنه:
$ stack exec hello
Please input your name: julie
Hi julie!
Who's a good puppy?!
YOU ARE!!!!!م. به طور پیشفرض، LineBuffering حالتِ بافِر شدنِ stdout ِه، یعنی خروجیها رو تا زمانی که یه خط جدید (\n) نبینه، بافِر میکنه (در حافظهی میانجی نگه میداره). NoBuffering این بافِر شدن رو (در صورت امکان) لغو میکنه.
میتونین برنامه رو بدونِ اون خطِ اول از main امتحان کنین و ببینین چطور تغییر میکنه (بعد از بازسازی). از این در بخشی از داربازی استفاده میکنیم، اما الان واجب نیست طرز کار توابعِ بافرینگ رو بدونین.