۱۳ - ۶توضیحات بیشتر از واردات ماژول‌ها

وارد کردنِ ماژول‌ها، توابعِ بیشتری، به غیر از اونهایی که در ‏‎Prelude‎‏ ِ استاندار هستن رو واردِ گستره می‌کنه. ماژولهای وارد شده در واقع تعاریفِ سطح بالا اند. همه‌ی چیزهایی هم که وارد شدن، مثل بقیه‌ی انقیادهای سطح بالا در گستره ِ تمامِ ماژول هستن، اما ممکنه توسطِ انقیادهای محلی تیره بشن. نوشتنِ چند تعریفِ وارات اثرِ افزایشی داره، اما ترتیبِ تعریف‌های وارداتی بی‌اهمیت ِه. اگه چیزی توسطِ هر کدوم از تعریف‌های وارداتی وارد شده باشه، در گستره ِ تمامِ ماژول قرار می‌گیره.

در فصل‌های قبل برای تمرین‌ها توابعی مثل ‏‎bool‎‏ و ‏‎toUpper‎‏ رو با وارد کردنِ ماژول‌هاشون (به ترتیب ‏‎Data.Bool‎‏ و ‏‎Data.Char‎‏) به داخلِ گستره آوردیم.

یه دوره کنیم ببینیم این کار رو چطور در GHCi انجام می‌دادیم. با استفاده از دستورِ ‏‎:browse‎‏ میشه توابعِ موجود در یه ماژولِ اسم‌دار رو نگاه کرد، اما با وارد کردنِ یه ماژول میشه از توابع‌ش استفاده کرد. مرور کردنِ ماژول‌هایی که هنوز وارد نشدن به دردِ وقتهایی می‌خوره که نمی‌دونیم تابعِ مورد نظرمون تو کدوم ماژول ِه:

Prelude> :browse Data.Bool
bool :: a -> a -> Bool -> a
(&&) :: Bool -> Bool -> Bool
data Bool = False | True
not :: Bool -> Bool
otherwise :: Bool
(||) :: Bool -> Bool -> Bool

Prelude> import Data.Bool

Prelude> :t bool
bool :: a -> a -> Bool -> a

در مثالِ بالا، به طورِ مقیدنشده همه چیز در ‏‎Data.Bool‎‏ رو وارد کردیم. اگه فقط ‏‎bool‎‏ از ‏‎Data.Bool‎‏ رو لازم داشتیم چطور؟

اول ‏‎Prelude‎‏ رو خاموش می‌کنیم تا هیچ کدوم از وارداتِ پیش‌فرض رو نداشته باشیم. برای این کار، موقعِ اجرای GHCi از یه توسعه ِ دیگه استفاده می‌کنیم. قبلاً طریقه‌ی استفاده از توسعه‌ها در فایل‌ها رو دیدین، اما اینجا همزمان با ورود به REPL، توسعه ِ ‏‎-XNoImplicitPrelude‎‏ رو وارد می‌کنیم:

-- این کار رو خارج از
-- هر پروژه‌ای انجام بدین

$ stack ghci –ghci-options -XNoImplicitPrelude
Prelude>

میشه ببینیم که ‏‎bool‎‏ و ‏‎not‎‏ دیگه در گستره نیستن:

Prelude> :t bool
<interactive>:1:1: Not in scope: ‘bool’
Prelude> :t not
<interactive>:1:1: Not in scope: ‘not’

بعد ‏‎Data.Bool‎‏ رو گزینشی وارد می‌کنیم، یعنی مشخص می‌کنیم که فقط می‌خوایم ‏‎bool‎‏ رو وارد کنیم:

Prelude> import Data.Bool (bool)
Prelude> :t bool
bool :: a -> a -> Bool -> a
Prelude> :t not
<interactive>:1:1: Not in scope: ‘not’

در ‏‎Prelude‎‏ به طور معمول ‏‎not‎‏ در گستره هست، اما ‏‎bool‎‏ نیست. پس می‌بینید که با خاموش کردنِ ‏‎Prelude‎‏ و خارج کردنِ توابعِ استانداردش از گستره، و بعد وارد کردنِ ‏‎bool‎‏ به تنهایی، دیگه تابعِ استانداردِ ‏‎not‎‏ رو در گستره نداریم.

میشه یک یا بیشترِ تابع‌های یه ماژول یا کتابخونه رو وارد کرد. گرامر ِش همونطوره که با GHCi نشون دادیم، فقط تعریف‌های وارداتی باید اولِ ماژول بیان. نوشتنِ ‏‎import Data.Char (toUpper)‎‏ در یه ماژول، باعث میشه تابعِ ‏‎toUpper‎‏، و نه هیچ چیزِ دیگه از ‏‎Data.Char‎‏ واردِ گستره ِ اون ماژول بشه.

برای مثال‌های پیشِ رو ‏‎Prelude‎‏ لازمه، پس لطفاً اول GHCi رو دوباره اجرا کنین.

وارداتِ مقیّد

اگه بخوایم بدونیم یه چیزی که وارد کردیم از کجا اومده چی کار کنیم؟ میشه از وارداتِ مقیّد استفاده کنیم تا اسم‌ها صریح‌تر بشن.

برای این کار از کلیدواژه ِ ‏‎qualified‎‏ در واردات استفاده می‌کنیم. گاهی پیش میاد چیزهای هم‌نامی رو از ماژولهای مختلف لازم دارین؛ مقیّد کردنِ واردات از چاره‌های رایج برای چنین مشکلی‌ه. با یه مثال نشون میدیم:

Prelude> import qualified Data.Bool
Prelude> :t bool

<interactive>:1:1:
    Not in scope: ‘bool’
    Perhaps you meant ‘Data.Bool.bool’

Prelude> :t Data.Bool.bool
Data.Bool.bool :: a -> a -> Bool -> a

Prelude> :t Data.Bool.not
Data.Bool.not :: Bool -> Bool

در این مثال همه چیز از ‏‎Data.Bool‎‏ واردِ گستره شده، اما فقط به همراهِ اسمِ ماژول ِشون قابلِ دسترسی‌اند. با این کار دیگه مشخصه که هر تابع از کجا اومده، که گاهی به درد می‌خوره.

وقت‌هایی که واردات ِمون رو مقیّد می‌کنیم، امکان انتساب یه مترادف یا مستعار هم برای اونها وجود داره تا مجبور نباشیم کلِ اسمِ ماژول رو تایپ کنیم:

Prelude> import qualified Data.Bool as B
Prelude> :t bool

<interactive>:1:1:
    Not in scope: ‘bool’
    Perhapse you meant ‘B.bool’

Prelude> :t B.bool
B. bool :: a -> a -> Bool -> a

Prelude> :t B.not
B.not :: Bool -> Bool

برای فایل‌ها هم به همین طریق (فقط باید اولِ ماژول نوشته بشن).

تعیینِ prompt

در مثالِ بالا وقتی ‏‎Data.Bool‎‏ رو به عنوانِ ‏‎B‎‏ وارد کردین، ممکنه متوجه شده باشین که prompt ِتون تغییر کرد:

Prelude> import qualified Data.Bool as B
Prelude B>

و اگه همه‌ی ماژول‌هایی که وارد کردین رو لازم داشته باشین و نخواین تخلیه‌شون کنین، prompt ِتون می‌تونه همینطور رشد کنه:

Prelude B> import Data.Char
Prelude B Data.Char>

(یادآوری: میشه با دستورِ ‏‎:m‎‏ ماژول‌ها رو تخلیه کرد و جلوی رشدِ prompt رو گرفت... البته این کار ماژول‌ها رو هم از گستره خارج می‌کنه!)

اگه بخواین جلوی این رشدِ بی‌رَویه‌ی prompt رو بگیرین، می‌تونین با دستورِ ‏‎:set‎‏، prompt ِتون رو به هر چیز که دوست دارین تغییر بدین:

Prelude B> :set prompt "Lambda> "
Lambda> import Data.Char
Lambda> :t B.bool
B.bool :: a -> a -> Bool -> a

با اینکه ‏‎Data.Bool‎‏ با نامِ ‏‎B‎‏ هنوز در گستره هست، ولی دیگه در prompt نیست. اگه بخواین prompt رو دائماً تغییر بدین، باید دستور ‏‎:set‎‏ رو در فایلِ تنظیمات ِ GHCi بنویسین. توضیحِ کاملِ این کار خارج از حوزه‌ی این فصل‌ه.

آنتراکت: ادراک‌تون رو چک کنید

این لیستِ واردات از یکی از ماژول‌ها در کتابخونه ِ کریس به اسمِ blacktip آورده شده:

import qualified Control.Concurrent
  as CC
import qualified Control.Concurrent.MVar
  as MV
import qualified Data.ByteString.Char8
  as B
import qualified Data.Locator
  as DL

import qualified Data.Time.Clock.POSIX
  as PSX
import qualified Filesystem
  as FS
import qualified Filesystem.Path.CurrentOS
  as FPC
import qualified Network.Info
  as NI

import qualified Safe
import Control.Exception (mask, try)
import Control.Monad (forever, when)
import Data.Bits
import Data.Bits.Bitwise (fromListBE)
import Data.List.Split (chunksOf)
import Database.Blacktip.Types
import System.IO.Unsafe (unsafePerformIO)

الان کاری نداریم که آیا این ماژول‌ها رو میشناسین یا نه. فقط واردات رو نگاه کنین و به سؤال‌های زیر جواب بدین:

۱.

از ‏‎Control.Monad‎‏ چه تابع‌هایی وارد شدن؟

۲.

کدوم ماژولها، هم مقیدنشده، و هم تماماً وارد شدن؟

۳.

فقط از روی اسم، فکر می‌کنین ‏‎Types‎‏ از ماژول ِ ‏‎blacktip‎‏ چه چیزهایی رو میاره؟

۴.

حالا یه بخشِ کوچیک از کُدِ ‏‎blacktip‎‏ رو با لیستِ بالا مقایسه کنیم:

writeTimestamp :: MV.MVar ServerState
               -> FPC.FilePath
               -> IO CC.ThreadId
writeTimestamp s path = do
  CC.forkIO go
  where go = forever $ do
          ss <- MV.readMVar s
          mask $ \_ -> do
            FS.writeFile path
            (B.pack (show (ssTime ss)))
          -- sleep for 1 second
          CC.threadDelay 1000000

a)

تایپ سیگنچر به سه تا واردات مستعار اشاره کرده. اونها استعار از کدوم ماژولها هستن؟

b)

تابعِ ‏‎FS.writeFile‎‏ به کدوم واردات اشاره داره؟

c)

‏‎forever‎‏ از کدوم واردات اومده؟