۷ - ۱۰نمایش ترکیب
اگه از فصل ۳ یادتون باشه، گفتیم که تابعهای 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
اساساً با ترکیب توابع سروکار داره، به همین خاطر هم به سبک بینقطه تابعِ خوبی شد.