۳ - ۳چاپ نوشتههای ساده
ببینیم چطور میشه نوشتهها رو در REPL چاپ کنیم:
Prelude> print "hello world!"
"hello world!"
اینجا با print
دستور چاپ رو به GHCi دادیم، اون هم چاپ کرد (با نقل قول ها دو طرفش). تابع print
مختصِ نوشتهها نیست، و برای چاپ تنوعی از دادهها استفاده میشه.
مثال زیر هم به GHCi دستور چاپ میده، ولی فقط برای تایپِ String
کار میکنه:
Prelude> putStrLn "hello world!"
hello world!
Prelude>
Prelude> putStr "hello world!"
hello world!Prelude>
احتمالاً متوجهِ فرق بین putStr
و putStrLn
شدین. اتفاق دیگهای هم که افتاد، چاپ نوشتهها بدون علائم نقل قول بود. دلیل این اتفاق تفاوت باطنی بین print
و این دو تابعه؛ شاید ظاهراً شبیه باشن، ولی تایپهاشون فرق داره. توابعی که ظاهر مشابه دارن، بسته به تایپ یا رَده ای که بهش تعلق دارن، ممکنه رفتار متفاوتی داشته باشن.
ببینیم چطور میشه همین کارها رو توی فایل انجام بدیم. کدهای زیر رو تو یه فایل به اسم print1.hs
بنویسین:
-- print1.hs
module Print1 where
main :: IO ()
main = putStrLn "hello world!"
وقتی این فایل رو تو GHCi لود کنید و main
رو اجرا کنین، چنین چیزی میبینید:
Prelude> :l print1.hs
[1 of 1] Compling Print1
Ok, modules loaded: Print1.
*Print1> main
hello world!
*Print1>
احتمالاً مثل بالا، prompt ِ شما هم به اسمِ ماژول تغییر کرده. اگه بخواین میتونین با دستورِ :module
یا :m
ماژول رو تخلیه کنین تا prompt دوباره Prelude
بشه. کار دیگهای هم که میشه کرد، تغییر prompt به یه نوشتهی دلخواهه، اینطوری با بارگذاری و تخلیه ِ ماژولها، تغییر نمیکنه*:
Prelude> :set prompt "new prompt!! "
new prompt!! :set prompt "\x03bb> "
λ> :r
Ok, modules loaded: Print1.
λ> main
hello world!
λ>
میتونین تغییرش رو با نوشتن اون دستور داخلِ فایلِ ~/.ghci
دائمی کنین. اگه این فایل وجود نداره، باید درستش کنید.
برگردیم به کد، وقتی یه برنامه رو میسازین یا تو REPL اجرا میکنین، main
دستورالعملِ پیشفرض برای اون برنامهست. main
در واقع تابع نیست، بلکه معمولاً یک سِری از دستورات برای اجراست، که ممکنه شاملِ اعمال توابع و ایجادِ عوارض هم باشند. موقعِ ساخت ِ پروژه با Stack، داشتنِ یه فایل به اسم Main.hs
که توش یه دستورالعمل ِ main
باشه، الزامیِه. ولی همونطور که قبلاً هم دیدیم، ممکنه فایلی main
نداشته باشه و بدون مشکل تو GHCi بارگذاریش کنیم.
همونطور که میبینین، main
دارای تایپِ IO ()
است. آی-او، یا I/O، مخففِ input/output یا ورودی/خروجی ِه. IO
یه تایپِ خاص در هسکله، و برای اجرای برنامههایی که علاوه بر محاسبهی توابع یا بیانیهها، شامل اثرات هم میشن به کار میره. چاپ روی صفحه یک اثر ِه، پس چاپِ خروجیِ یک ماژول هم باید داخل این تایپِ IO
پوشونده بشه. موقعهایی که یه تابع رو مستقیماً در REPL وارد میکنین، GHCi به طور ضمنی تشخیص میده و IO
رو پیاده میکنه. چون اجراییه ِ main
، دستورالعمل ِ پیشفرضِه، ما هم از اینجا به بعد در خیلی از فایلهایی که درست میکنیم، قرارش میدیم. در یکی از فصلهای آینده، این مطالبی که گفتیم رو با جزئیات بیشتری بررسی میکنیم.
یه فایل دیگه درست کنیم:
-- print2.hs
module Print2 where
main :: IO ()
main = do
putStrLn "Count to four for me:"
putStr "one, two"
putStr ", three, and"
putStrLn " four!"
گرامر ِ do
، یه گرامر ِ خاصه، که برای تسلسل ِ اجراییهها کاربرد داره؛ و عموماً برای متسلسل کردن ِ اجراییههایی که برنامه رو تشکیل میدن (و بعضیهاشون الزاماً یه اثر، مثل چاپ به صفحه، رو هم اجرا میکنند) استفاده میشه. به همین خاطر، تایپِ main
باید IO ()
باشه. این نوشتارِ do واجب نیست، ولی خیلی خواناتر از کدِ معادلِشه.
کد بالا رو که اجرا کنید:
Prelude> :l print2.hs
[1 of 1] Compling Print2
Ok, modules loaded: Print2.
*Print2> main
Count to four for me:
one, two, three, four!
*Print2>
برای سرگرمی، putStr
ها و putStrLn
ها رو با هم جابجا کنین و نتیجهها رو ببینین. Ln
در putStrLn
یعنی بعد از چاپِ متن، خط ِ جدیدی شروع میکنه.
الحاق ِ نوشتهها
الحاق کردن، یعنی به هم زنجیرکردن؛ و در برنامهنویسی، معمولاً برای تسلسلهای خطی مثل لیستها و نوشتهها بیان میشه. اگه نوشتههای "Curry"
و " Rocks!"
رو به هم الحاق کنیم، حاصلش میشه "Curry Rocks!"
. به اون فاصله ِ اولِ " Rocks!"
دقت کنین. بدونِ اون فاصله، جواب میشه "CurryRocks!"
.
کد زیر رو تو یه فایل جدید بنویسیم:
-- print3.hs
module Print3 where
myGreeting :: String
myGreeting = "hello" ++ " world!"
hello :: String
hello = "hello"
world :: String
world = "world!"
main :: IO ()
main = do
putStrLn myGreeting
putStrLn secondGreeting
where secondGreeting =
concat [hello, " ", world]
با استفاده از ::
تایپِ همهی بیانیههای سطح بالا رو تعیین کردیم. البته کامپایلر خودش میتونه تایپها رو استنتاج کنه* و نیازی به نوشتنِ تایپ سیگنچرِ بیانیهها نیست، با این حال، عادت به نوشتن اونها برای برنامههای بزرگتر مفیده.
م. به این قابلیتِ کامپایلر، type inference گفته میشه.
گفتیم String
و [Char]
مترادف تایپیاند. میتونین با تغییرِ تایپ سیگنچرها در مثالِ آخر، و اجرای برنامهتون، امتحان کنین ببینین چیزی تغییر میکنه یا نه.
اگه برنامه رو اجرا کنیم:
Prelude> :l print3.hs
[1 of 1] Compling Print3
Ok, modules loaded: Print3.
*Print3> main
hello world!
hello world!
*Print3>
در این مثالِ ساده، چندتا چیز رو نشون دادیم:
۱.
مقادیر رو در سطحِ بالا ِ برنامه تعریف کردیم: myGreeting
، hello
، world
، و main
. منظور اینه که اونها در کلِ ماژول قابل دسترسی بودند.
۲.
تایپِ تعاریفِ سطح بالا رو صراحتاً مشخص کردیم.
۳.
نوشتهها رو با (++)
و concat
به هم الحاق کردیم.