۷ - ۱۰نمایش ترکیب
اگه از فصل ۳ یادتون باشه، گفتیم که تابعهای print و putStr در ظاهر مشابهاند، اما به خاطرِ تایپ متفاوتشون رفتار متفاوتی هم دارن. حالا دقیقتر به این مورد نگاه میکنیم.
اول اینکه، putStrLn و putStr تایپِ یکسان دارن:
putStr :: String -> IO ()
putStrLn :: String -> IO ()اما تایپِ print فرق داره:
print :: Show a => a -> IO ()همهشون جواب با تایپِ IO () میدن، که دلایلش رو در فصلِ قبل توضیح دادیم. ولی پارامترهاشون فرق میکنن. دو تابعِ اول String میگیرن، اما print یک پارامتر با پلیمورفیسمِ محدود ِ Show a => a داره. دو تابعِ اول برای نمایشِ مقادیری که به خودیِخود String هستن مناسباند. ولی اعداد (یا هر مقدارِ غیرِ نوشته) رو چطور نشون بدیم؟ اول باید به نوشته تبدیلشون کنیم، بعد میتونیم اون نوشتهها رو چاپ کنیم.
شاید از توضیحاتمون دربارهی تایپکلاسِ Show، تابعِ show یادتون باشه. تایپش این بود:
show :: Show a => a -> Stringخوشبختانه رایج بودنِ ترکیب ِ putStrLn و show درک شده بود، و در نتیجه تابعِ print که ترکیب ِ دو تابعِ show و putStrLn هست تعبیه شده. دلیل این کار هم سادگی ِ بیشترِشه. تابعِ چاپ فقط به چاپ کردن اهمیت میده، و تابعِ تبدیل به نوشته یا show هم فقط به کار خودش تمرکز میکنه.
در زیر دو راه برای نوشتنِ تابعِ print با putStrLn و show رو آوردیم:
print :: Show a => a -> IO ()
print a = putStrLn (show a)
-- استفاده از اوپراتورِ . برای
-- ترکیب توابع
(.) :: (b -> c) -> (a -> b) -> a -> c
-- رو اینطوری هم میشه تعریف کرد print
print :: Show a => a -> IO ()
print a = (putStrLn . show) aحالا این کاربردِ (.)، putStrLn، و show رو مرحله به مرحله بررسی کنیم:
(.) :: (b -> c) -> (a -> b) -> a -> c
putStrLn :: String -> IO ()
-- [1] [2]
show :: Show a => a -> String
-- [3] [4]
putStrLn . show :: Show a => a -> IO ()
-- [5] [6]
(.) :: (b -> c) -> (a -> b) -> a -> c
-- [1] [2] [3] [4] [5] [6]
-- اگه متغیرهای تایپی رو با تایپهای
-- :مختصِ این کاربردِ (.) جایگزین کنیم
(.) :: Show a => (String -> IO ())
-> (a -> String)
-> a -> IO ()
(.) :: (b -> c)
-- (String -> IO ())
-> (a -> b)
-- (a -> String)
-> a -> c
-- a -> IO ()۱.
نوشتهای که putStrLn به عنوانِ آرگومان قبول میکنه.
۲.
IO () که putStrLn برمیگردونه، یعنی اثر جانبی (که همون چاپ هست) رو پیاده میکنه و مقدارِ واحد رو برمیگردونه.
۳.
تایپ a که باید تایپکلاس Show رو داشته باشه؛ این همون Show a => a از تابع show ِه (یکی از متودهای تایپکلاس Show).
۴.
این نوشتهایه که show برمیگردونه. همون چیزیه که مقدارِ Show a => a بهش تبدیل میشه.
۵.
تایپِ Show a => a ی که تابعِ ترکیبشده ِ نهایی انتظار داره.
۶.
IO () ای که تابعِ ترکیبشده ِ نهایی برمیگردونه.
حالا میتونیم به سبکِ بینقطه بنویسیمش. وقتی بیشتر با ترکیب ِ توابع کار داریم تا اعمال ِشون، ممکنه نسخهی بینقطه گاهی اوقات (نه همیشه) خوشایندتر باشه.
این نسخهی قبلیِ تابع:
print :: Show a => a -> IO ()
print a = (putStrLn . show) aو این هم نسخهی بینقطه از print:
print :: Show a => a -> IO ()
print = putStrLn . showهدفِ اصلیِ print، ترکیب ِ putStrLn و show ِه تا مجبور نباشیم تابعِ show رو خودمون صدا بزنیم. به کلامِ دیگه، print اساساً با ترکیب توابع سروکار داره، به همین خاطر هم به سبک بینقطه تابعِ خوبی شد.