۱۳ - ۷تعاملیسازی برنامه
حالا میخوایم کاری کنیم که برنامه اول اسم ِتون رو بپرسه، بعد هم بهتون سلام کنه. اول تابعِ 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
امتحان کنین و ببینین چطور تغییر میکنه (بعد از بازسازی). از این در بخشی از داربازی استفاده میکنیم، اما الان واجب نیست طرز کار توابعِ بافرینگ رو بدونین.