۱۳ - ۱۰قدم اول: وارد کردن ماژول‌ها

-- src/Main.hs

module Main where

import Control.Monad (forever) -- [1]
import Data.Char (toLower) -- [2]
import Data.Maybe (isJust) -- [3]
import Data.List (intersperse) -- [4]
import System.Exit (exitSuccess) -- [5]
import System.Random (randomRIO) -- [6]

هر کدوم از واردات رو عددگذاری کردیم. شما لازم نیست اون کامنت‌های عددی رو بنویسین. همه‌ی ماژول‌هایی که این زیر لیست کردیم، بخشی از کتابخونه ِ ‏‎base‎‏ که همراه با GHC نصب میشه هستن، مگر غیرش رو گفته باشیم.

۱.

تابعِ ‏‎forever‎‏ از ماژول ِ ‏‎Control.Monad‎‏ رو برای ایجاد یه حلقه‌ی بینهایت استفاده می‌کنیم. دو تا نکته:

a)

برای این کار ‏‎forever‎‏ واجب نیست، اما ما ازش استفاده می‌کنیم.

b)

انتظار نمیره که بفهمین دقیقاً چطور یا چه کاری انجام میده. اساساً بجای اینکه یک بار یه تابع محاسبه بشه، میذاره اون تابع رو بینهایت بار اجرا کنیم، تا وقتی کاری کنیم برنامه خارج بشه یا شکست بخوره.

۲.

تابعِ ‏‎toLower‎‏ از ماژول ِ ‏‎Data.Char‎‏ رو برای تبدیلِ تمام حروفِ نوشته‌مون به حروف کوچک استفاده می‌کنیم:

λ> import Data.Char (toLower)
λ> toLower 'A'
'a'

حواستون باشه که اگه حرفی معادل کوچکی نداره، ‏‎toLower‎‏ همون حرف رو برمی‌گردونه:

λ> toLower ':'
':'

۳.

تابعِ ‏‎isJust‎‏ از ماژول ِ ‏‎Data.Maybe‎‏ رو برای تشخیص اینکه آیا همه‌ی حرف‌های لغتِ موردِ نظر پیدا شده یا نه استفاده می‌کنیم:

λ> import Data.Maybe (isJust)
λ> isJust Nothing
False
λ> isJust (Just 10)
True

این تابع رو با ‏‎all‎‏ (یکی از توابع استانداردِ ‏‎Prelude‎‏) استفاده می‌کنیم. کارِ این تابع رو میشه اینطور توصیف کرد: "با فرض تابعی که به ازای هر المان، ‏‎True‎‏ یا ‏‎False‎‏ برمی‌گردونه، آیا برای همه‌ی اون المان‌ها ‏‎True‎‏ برمی‌گردونه؟"

λ> all even [2, 4, 6]
True
λ> all even [2, 4, 7]
False
λ> all isJust [Just 'd', Nothing, Just 'g']
False
λ> all isJust [Just 'd',Just 'o',Just 'g']
True

تایپِ تابعِ ‏‎all‎‏ از این قراره:

Foldable t => (a -> Bool) -> t a -> Bool

هنوز تایپکلاسِ ‏‎Foldable‎‏ رو توضیح ندادیم. برای فعلاً می‌تونین ‏‎Foldable‎‏ رو مجموعه‌ای از عملیات برای تایپ‌هایی که قابلیتِ فولد شدن (به نحوی مشابه لیست‌ها) رو دارن فرض کنین، اما اصلاً واجب نیست مثل لیست‌ها یا نوع‌داده‌های مشابه، بیشتر از یک مقدار داشته باشن (یا ممکنه اصلاً هیچ مقداری نداشته باشن). با یه تایپ سیگنچر میشه تایپ‌ش رو مشخص‌تر کرد:

λ> :t all :: (a -> Bool) -> [a] -> Bool
all :: (a -> Bool) -> [a] -> Bool

این برای هر تایپی که یه نمونه از تایپکلاسِ ‏‎Foldable‎‏ داره کار می‌کنه:

λ> :t all :: (a -> Bool) -> Maybe a -> Bool
all :: (a -> Bool) -> Maybe a -> Bool

-- به ترتیب متغیرهای تایپ دقت کنین
-- خودتون جداگانه امتحان کنین
λ> :t all :: (a -> Bool) -> Either b a -> Bool
all :: (a -> Bool) -> Either b a -> Bool

اما اگه نوع‌داده، نمونه از ‏‎Foldable‎‏ نداشته باشه کار نمی‌کنه:

> :t all :: (a -> Bool) -> (b -> a) -> Bool

o instance for (Foldable ((->) b1)) arising
rom a use of ‘all’

n the expression:
 all :: (a -> Bool) -> (b -> a) -> Bool

۴.

تابعِ ‏‎intersperse‎‏ از ماژول ِ ‏‎Data.List‎‏ رو برای میان‌پاشی ِ المان‌های لیست استفاده می‌کنیم. اینجا می‌خوایم بین حروفی که بازیکن حدس زده فاصله بذاریم. شاید یادتون باشه که در فصلِ توابعِ بازگشتی از ‏‎intersperse‎‏ برای گذاشتنِ خط فاصله استفاده کردیم (در تمرینی که می‌خواستیم اعداد رو به لغات تبدیل کنیم):

Prelude> import Data.List (intersperse)
Prelude> intersperse ' ' "Blah"
"B l a h"

تایپِ ‏‎intersperse‎‏ محدود به استفاده برای حرف‌ها و نوشته‌ها نیست، پس میشه برای لیست‌های حاوی المان‌های با تایپ‌های گوناگون هم ازش استفاده کرد:

Prelude> :t intersperse
intersperse :: a -> [a] -> [a]

Prelude> intersperse 0 [1, 1, 1]
[1,0,1,0,1]

۵.

تابعِ ‏‎exitSuccess‎‏ از ماژول ِ ‏‎System.Exit‎‏ رو برای خروج ِ موفقیت‌آمیز استفاده می‌کنیم – یعنی خطایی نیست و فقط کارمون تموم شده. اینکه موفقیت‌آمیز بوده یا نه رو تعیین می‌کنیم تا اگه خطایی اتفاق افتاده بود، سیستم عامل بدونه. دقت کنین که اگه ‏‎exitSuccess‎‏ رو در REPL حساب کنین، استثنا میده. تو یه برنامه‌ی معمولی که استثنا رو نمی‌گیره، فقط کلِ برنامه رو تموم می‌کنه.

۶.

تابعِ ‏‎randomRIO‎‏ از ماژول ِ ‏‎System.Random‎‏ رو برای انتخاب تصادفی ِ لغت از دیکشنری‌مون استفاده می‌کنیم. ماژول ِ ‏‎System.Random‎‏ از کتابخونه ِ ‏‎random‎‏ ِه. باز هم باید کتابخونه رو در گستره داشته باشین تا REPL بتونه بارگذاری‌ش کنه. همین که بیاد در گستره، میشه با ‏‎randomRIO‎‏ یه عددِ تصادفی بگیریم. از تایپ سیگنچر ِش می‌تونین ببینین که آرگومانِ ورودی‌ش یه توپل ِه. این توپل در واقع بازه‌ایه که از داخلِ اون یکی رو به طورِ تصادفی انتخاب می‌کنه:

Prelude> import System.Random
Prelude System.Random> :t randomRIO
randomRIO :: Random a => (a, a) -> IO a
Prelude System.Random> randomRIO (0, 5)
4
Prelude System.Random> randomRIO (1, 100)
71
Prelude System.Random> randomRIO (1, 100)
12

بعداً از این برای ایجاد یه اندیس ِ تصادفی که بتونیم باهاش از لیستِ لغات‌مون یه کلمه رو برداریم استفاده می‌کنیم.